当前位置:  开发笔记 > 编程语言 > 正文

C#或.NET中最糟糕的问题是什么?

如何解决《C#或.NET中最糟糕的问题是什么?》经验,为你挑选了45个好方法。

我最近在使用一个DateTime对象,并写了这样的东西:

DateTime dt = DateTime.Now;
dt.AddDays(1);
return dt; // still today's date! WTF?

intellisense文档AddDays()说它增加了一天的日期,它没有 - 它实际上返回添加了一天的日期,所以你必须写如下:

DateTime dt = DateTime.Now;
dt = dt.AddDays(1);
return dt; // tomorrow's date

这个曾经多次咬过我,所以我认为编制最糟糕的C#陷阱会很有用.



1> Eric Z Beard..:
private int myVar;
public int MyVar
{
    get { return MyVar; }
}

Blammo.您的应用程序崩溃,没有堆栈跟踪.一直发生.

(在getter中注意资本MyVar而不是小写myVar.)


@jrista:哦,请不要......不是......恐怖......
和SO适合这个网站:)
我在私人会员身上加了下划线,帮了很多忙!
我尽可能使用自动属性,停止这种问题很多;)
这是为你的私人领域使用前缀的一个很好的理由(还有其他的,但这是一个很好的):_ myVar,m_myVar
Resharper说:"函数在所有路径上都是递归的." ,将其加下划线,并在断点栏中放置一个带箭头的圆圈图标.那会给我留下足够的红旗!(resharper的无耻插件......你可以避免所有那些丑陋的m_变量)
@fretje:如果你喜欢崩溃比m_更好......
如果我没弄错的话,resharper会警告你这个可能的问题
你应该永远不要将两个成员命名为同一个单词而只有个案差异的原因有很多.其中一个原因是VB.NET不区分大小写,并且任何暴露给VB.NET的C#接口都只会显示第一个类似命名的变量供消费.
在设计时暴露属性时,我实际上已经看到这种事情导致VS ide崩溃.
只需将_myVariableName用于私有字段.
前几天做了这件事,看着我的代码,我意识到我正在调用该物业内的财产.无论谁说使用m_都应该被枪杀;-)
结果是,如果你曾经测试过它,那么在发布的代码中很可能不会发生这种情况.与此线程中的许多其他陷阱不同,这个代码在第一次执行时被捕获.
由于其邪恶和Stack Overflow相关的讽刺,这一奖项获奖.
覆盖属性并忘记放置base.PropertyName而不是PropertyName时,我遇到了同样的问题.
我不喜欢'm_','s_'等前缀,所以我更喜欢引用像`this.fieldName`这样的字段而不仅仅是`fieldName`.对于静态字段,我使用下划线前缀:`_staticFieldName`,因为缺乏更好的想法.

2> Jon Skeet..:

Type.GetType

我见过的人咬了很多人Type.GetType(string).他们想知道为什么它适用于他们自己的装配中的类型System.String,有些类型,但不是System.Windows.Forms.Form.答案是它只能查看当前的程序集mscorlib.


匿名方法

C#2.0引入了匿名方法,导致这样的恶劣情况:

using System;
using System.Threading;

class Test
{
    static void Main()
    {
        for (int i=0; i < 10; i++)
        {
            ThreadStart ts = delegate { Console.WriteLine(i); };
            new Thread(ts).Start();
        }
    }
}

打印出来的是什么?嗯,这完全取决于时间安排.它将打印10个数字,但它可能不会打印0,1,2,3,4,5,6,7,8,9,这是您可能期望的.问题在于它是i已捕获的变量,而不是在创建委托时的值.这可以使用正确范围的额外局部变量轻松解决:

using System;
using System.Threading;

class Test
{
    static void Main()
    {
        for (int i=0; i < 10; i++)
        {
            int copy = i;
            ThreadStart ts = delegate { Console.WriteLine(copy); };
            new Thread(ts).Start();
        }
    }
}

延迟执行迭代器块

这个"穷人的单位测试"没有通过 - 为什么不呢?

using System;
using System.Collections.Generic;
using System.Diagnostics;

class Test
{
    static IEnumerable CapitalLetters(string input)
    {
        if (input == null)
        {
            throw new ArgumentNullException(input);
        }
        foreach (char c in input)
        {
            yield return char.ToUpper(c);
        }
    }

    static void Main()
    {
        // Test that null input is handled correctly
        try
        {
            CapitalLetters(null);
            Console.WriteLine("An exception should have been thrown!");
        }
        catch (ArgumentNullException)
        {
            // Expected
        }
    }
}

答案是在第一次调用CapitalLetters迭代器的MoveNext()方法之前,代码源中的代码不会被执行.

我的脑筋急转弯页面上还有其他一些奇怪的东西.


迭代器的例子是狡猾的!
实际上,如果您提供AssemblyQualifiedName,则Type.GetType有效.Type.GetType("System.ServiceModel.EndpointNotFoundException,System.ServiceModel,Version = 3.0.0.0,Culture = neutral,PublicKeyToken = b77a5c561934e089");
@chakrit:回想起来,这可能是一个好主意,但我认为现在为时已晚.它也可能看起来像我只是想获得更多的代表......
为什么不把它分成3个答案所以我们可以把每个人投票而不是全部投票?
@kentaromiura:重载解析从最派生类型开始并处理树 - 但只查看最初在它正在查看的类型中声明*的方法*.Foo(int)会覆盖基本方法,因此不予考虑.Foo(对象)适用,因此重载决策停止.奇怪,我知道.
@FosterZ:它正在创建一个ThreadStart类型的委托,它将`i`的当前值打印到控制台.

3> Sam Saffron..:
重新抛出异常

获得大量新开发人员的问题是重新抛出异常语义.

很多时候我看到如下代码

catch(Exception e) 
{
   // Do stuff 
   throw e; 
}

问题是它会擦除堆栈跟踪并使诊断问题更加困难,导致无法跟踪异常的来源.

正确的代码是没有args的throw语句:

catch(Exception)
{
    throw;
}

或者将异常包装在另一个异常中,并使用内部异常来获取原始堆栈跟踪:

catch(Exception e) 
{
   // Do stuff 
   throw new MySpecialException(e); 
}


@Kyralessa:有很多情况:例如,如果要在调用者获取异常之前回滚事务.你回滚然后重新抛出.
我总是看到人们抓住并重新抛出异常只是因为他们被教导他们必须捕获所有异常,而不是意识到它会被调用堆栈进一步捕获.它让我疯了.
@Kyralessa最大的例子就是你必须做日志记录.在catch中记录错误,然后重新抛出..

4> Shaul says I..:

海森堡观察窗

如果您正在进行按需加载的操作,这可能会让您感到非常不满,例如:

private MyClass _myObj;
public MyClass MyObj {
  get {
    if (_myObj == null)
      _myObj = CreateMyObj(); // some other code to create my object
    return _myObj;
  }
}

现在让我们假设你在其他地方有一些代码使用它:

// blah
// blah
MyObj.DoStuff(); // Line 3
// blah

现在您要调试CreateMyObj()方法.所以你在上面的第3行放了一个断点,打算进入代码.只是为了好的衡量,你还在上面的线上设置了一个断点_myObj = CreateMyObj();,甚至还有一个断点CreateMyObj().

代码命中第3行的断点.您可以进入代码.你期望输入条件代码,因为_myObj它显然是空的,对吗?呃......所以......为什么它会跳过这个条件并直接进入return _myObj?!你将鼠标悬停在_myObj上......事实上,它确实有价值!那是怎么发生的?!

答案是您的IDE导致它获得一个值,因为您打开了一个"监视"窗口 - 尤其是"Autos"监视窗口,它显示与当前或上一行执行相关的所有变量/属性的值.当你打你的断点在第3行,监视窗口中决定,你有兴趣知道的价值MyObj-所以在幕后,忽略任何断点的,它去和计算的价值MyObj为你- 包括调用CreateMyObj()该设置_myObj的值!

这就是为什么我称之为海森堡观察窗 - 你不能在不影响它的情况下观察它的价值...... :)

GOTCHA!


编辑 - 我觉得@ChristianHayter的评论值得包含在主答案中,因为它看起来像是这个问题的有效解决方法.所以,只要你有一个懒惰的属性......

使用[DebuggerBrowsable(DebuggerBrowsableState.Never)]或[DebuggerDisplay("")]装饰属性. - 克里斯蒂安·海特


用`[DebuggerBrowsable(DebuggerBrowsableState.Never)]`或`[DebuggerDisplay("")]`装饰你的属性.
我甚至徘徊在变量上,而不仅仅是观察窗口.
精彩的发现!你不是程序员,你是一个真正的调试器.
如果您正在开发框架类并希望监视窗口功能而不改变延迟构造的属性的运行时行为,则可以使用调试器类型代理返回已经构造的值,以及属性没有的消息如果是这样的话就被建造了.`Lazy `类(特别是它的`Value`属性)是使用它的一个例子.
我记得有人(由于某种原因我无法理解)在`ToString`的重载中改变了对象的值.每当他徘徊在它上面时,工具提示给了他不同的价值 - 他无法弄明白......
这让我上周疯狂,但对于一个静态构造函数而不是一个懒惰的加载器.无论我怎么努力,我都无法达到我的断点,即使代码显然正在运行.结束了必须使用低技术的`Debug.WriteLine`方法.

5> Jon B..:

这是另一次让我:

static void PrintHowLong(DateTime a, DateTime b)
{
    TimeSpan span = a - b;
    Console.WriteLine(span.Seconds);        // WRONG!
    Console.WriteLine(span.TotalSeconds);   // RIGHT!
}

TimeSpan.Seconds是时间跨度的秒部分(2分0秒的秒值为0).

TimeSpan.TotalSeconds是以秒为单位测量的整个时间跨度(2分钟的总秒数值为120).


@MusiGenesis属性很有用.如果我想显示分块的时间跨度怎么办?例如,假设您的Timespan代表"3小时15分10秒"的持续时间.如何在没有秒,小时,分钟属性的情况下访问此信息?
在重新阅读本文时,我不得不想知道为什么`TimeSpan`甚至*都有*一个'Seconds`属性.无论如何,谁给了老鼠的屁股什么时间跨度的秒数?它是一个任意的,与单位有关的值; 我无法想象它的任何实际用途.
对我来说有意义的是TimeSpan.TotalSeconds将返回...时间跨度中的总秒数.

6> Timothy Walt..:

泄漏内存,因为您没有取消挂钩事件.

这甚至让我认识的一些高级开发人员感到惊讶

想象一下WPF表单中有很多东西,在那里你订阅了一个事件.如果您没有取消订阅,则在关闭和取消引用后,整个表单将保留在内存中.

我相信我看到的问题是在WPF表单中创建一个DispatchTimer并订阅Tick事件,如果你不做 - =在计时器上你的表单泄漏了内存!

在这个示例中,您的拆卸代码应具有

timer.Tick -= TimerTickEventHandler;

这个特别棘手,因为你在WPF表单中创建了DispatchTimer的实例,所以你会认为它将是垃圾收集过程处理的内部引用...不幸的是,DispatchTimer使用静态的内部订阅和服务列表UI线程上的请求,因此引用由静态类"拥有".


有一个关于弱参考事件的MS-connect建议[这里](https://connect.microsoft.com/VisualStudio/feedback/details/94154/add-support-for-weak-reference-event-handlers)会解决这个问题,虽然在我看来,我们应该完全用弱耦合的事件模型替换非常差的事件模型,就像CAB使用的那样.

7> jdehaan..:

也许不是真的有问题,因为这种行为在MSDN中写得很清楚,但是我的脖子已经坏了一次,因为我觉得它很反直觉:

Image image = System.Drawing.Image.FromFile("nice.pic");

这个人将"nice.pic"文件锁定,直到图像被丢弃.在我遇到它的时候,我虽然在飞行中加载图标并且没有意识到(起初)我最终得到了数十个打开和锁定的文件会很好!图像跟踪它从哪里加载文件...

怎么解决这个?我以为一个班轮就可以完成这项任务.我期待一个额外的参数FromFile(),但没有,所以我写了这个......

using (Stream fs = new FileStream("nice.pic", FileMode.Open, FileAccess.Read))
{
    image = System.Drawing.Image.FromStream(fs);
}


需要检查一些代码.BRB.
我同意这种行为毫无意义.除了"这种行为是设计的"之外,我找不到任何解释.
@EsbenSkovPedersen这么简单但有趣且干燥的评论.让我的一天.

8> Erik van Bra..:

如果算上ASP.NET,我会说webforms生命周期对我来说是一个非常大的问题.我花了无数个小时来调试写得不好的webforms代码,只是因为很多开发人员并不真正理解何时使用哪个事件处理程序(遗憾的是我包括在内).


还有另外一个专门针对ASP.NET陷阱的问题(理所当然).ASP.NET的基本概念(使Web应用程序看起来像开发人员的Windows应用程序)是如此可怕的误导,我不确定它甚至算作"陷阱".
这就是为什么我搬到了MVC ......看来头痛......
@MusiGenesis现在看来误导,但在当时,人们希望自己的Web应用程序(应用程序是关键字 - ASP.NET的WebForms是不是真的设计举办一个博客)有同样的表现为他们的Windows应用程序.这种情况最近才发生变化,很多人仍然"不在那里".整个问题是抽象过于漏洞 - 网络表现得不像桌面应用程序那么多*它几乎导致每个人都感到困惑.

9> Jimmy..:

重载==运算符和无类型容器(arraylists,数据集等):

string my = "my ";
Debug.Assert(my+"string" == "my string"); //true

var a = new ArrayList();
a.Add(my+"string");
a.Add("my string");

// uses ==(object) instead of ==(string)
Debug.Assert(a[1] == "my string"); // true, due to interning magic
Debug.Assert(a[0] == "my string"); // false

解决方案?

string.Equals(a, b)在比较字符串类型时总是使用

使用泛型List来确保两个操作数都是字符串.


是的,C#语言最大的缺陷之一是Object类中的==运算符.他们应该强迫我们使用ReferenceEquals.
你在那里有额外的空间使它完全错误 - 但如果你把空格拿出来,最后一行仍然是真的,因为"我的"+"字符串"仍然是一个常数.
值得庆幸的是,自2.0以来我们已经有了泛型.如果您在上面的示例中使用List 而不是ArrayList,则不必担心.而且我们已经从中获得了表现,耶!我总是在遗留代码中根除对ArrayLists的旧引用.

10> Nicolas Dori..:
[Serializable]
class Hello
{
    readonly object accountsLock = new object();
}

//Do stuff to deserialize Hello with BinaryFormatter
//and now... accountsLock == null ;)

故事的道德:反序列化对象时不会运行字段初始化器


是的,我讨厌.NET序列化而不运行默认构造函数.我希望在不调用任何构造函数的情况下构造一个对象是不可能的,但实际上并非如此.

11> Maltrap..:

DateTime.ToString("dd/MM/yyyy") ; 这实际上并不总能给你dd/MM/yyyy,而是会考虑区域设置并根据你所在的位置替换你的日期分隔符.所以你可能会得到dd-MM-yyyy或类似的东西.

正确的方法是使用DateTime.ToString("dd'/'MM'/'yyyy");


DateTime.ToString("r")应该转换为使用GMT的RFC1123.GMT距离UTC只有几分之一秒,但"r"格式说明符不会转换为UTC,即使有问题的DateTime被指定为Local.

这导致以下问题(取决于您的本地时间与UTC的距离):

DateTime.Parse("Tue, 06 Sep 2011 16:35:12 GMT").ToString("r")
>              "Tue, 06 Sep 2011 17:35:12 GMT"

哎呦!


实际上我相信这样做的正确方法是`DateTime.ToString("dd/MM/yyyy",CultureInfo.InvariantCulture);`
将mm更改为MM - mm是分钟,MM是几个月.另一个问题,我猜......
我认为本地化的默认值比其他方式更差.至少开发人员完全忽略了本地化,代码*在本地化不同的机器上工作*.这样,代码可能不起作用.
@Beska:因为您要写入文件,所以需要使用指定日期格式的特定格式.

12> Mitchel Sell..:

前几天我看到这张贴出来了,我觉得它很晦涩,对那些不知道的人来说很痛苦

int x = 0;
x = x++;
return x;

因为那将返回0而不是大多数人预期的1


我希望实际上不会咬人 - 我真的希望他们不会在第一时间写出来!(当然,这很有意思.)
@Kevin:我不认为这很简单.如果x = x ++相当于x = x后跟x ++,那么结果将是x = 1.相反,我认为发生的事情是首先评估等号右边的表达式(给出0),然后x是递增(给出x = 1),最后执行赋值(再次给出x = 0).
我不认为这是非常模糊的......
至少,在C#中,如果意外,则定义结果.在C++中,它可以是0或1,或任何其他结果,包括程序终止!
这不是问题; x = x ++ - > x = x,然后递增x .... x = ++ x - >增量x然后x = x
如果我理解正确,它在功能上等同于int temp = x; X ++; x = temp;
我明年正在教C#,这正是学生可能会做的事情!如果你没想到它可以成为一个可以检测的试验.
-1:这完全符合预期,因为C#的执行顺序非常明确.
它根本不是那么明显.在C/C++中,这实际上是未定义的代码 - x可以是0或1,具体取决于编译器!

13> Damovisa..:

我参加这个聚会有点晚了,但我最近有两个问题都咬了我:

日期时间分辨率

Ticks属性测量时间为百万分之一秒(100纳秒块),但分辨率不是100纳秒,大约是15毫秒.

这段代码:

long now = DateTime.Now.Ticks;
for (int i = 0; i < 10; i++)
{
    System.Threading.Thread.Sleep(1);
    Console.WriteLine(DateTime.Now.Ticks - now);
}

会给你一个输出(例如):

0
0
0
0
0
0
0
156254
156254
156254

类似地,如果你查看DateTime.Now.Millisecond,你将获得15.625ms的圆形块的值:15,31,46等.

这种特殊行为因系统而异,但在此日期/时间API中还有其他与分辨率相关的问题.


Path.Combine

组合文件路径的好方法,但它并不总是按照您期望的方式运行.

如果第二个参数以\字符开头,则不会为您提供完整的路径:

这段代码:

string prefix1 = "C:\\MyFolder\\MySubFolder";
string prefix2 = "C:\\MyFolder\\MySubFolder\\";
string suffix1 = "log\\";
string suffix2 = "\\log\\";

Console.WriteLine(Path.Combine(prefix1, suffix1));
Console.WriteLine(Path.Combine(prefix1, suffix2));
Console.WriteLine(Path.Combine(prefix2, suffix1));
Console.WriteLine(Path.Combine(prefix2, suffix2));

给你这个输出:

C:\MyFolder\MySubFolder\log\
\log\
C:\MyFolder\MySubFolder\log\
\log\


在~15ms间隔内的时间量化并不是因为基础定时机制缺乏准确性(我之前没有详细说明).这是因为您的应用程序在多任务操作系统中运行.Windows每15分钟左右检查一次您的应用程序,并且在它获得的小时间片段内,您的应用程序会处理自上次切片以来排队的所有消息.您在该切片中的所有调用都返回完全相同的时间,因为它们都是在有效的同时完成的.
额外的'\'是许多unix/mac/linux人的问题.在Windows中,如果有一个前导'\',那就意味着我们想要去驱动器的根(即C:\)在`CD`命令中尝试看看我的意思.... 1)转到`C:\Windows\System32` 2)键入`CD\Users` 3)哇!现在你在`C:\ Users` ... GOT IT?... Path.Combine(@"C:\ Windows\System32",@"\ Users")应返回`\ Users`,这意味着'[current_drive_here]:\ Users`
即使没有"睡眠",这也是一样的.这与每15毫秒安排一次的应用程序无关.DateTime.UtcNow,GetSystemTimeAsFileTime调用的本机函数似乎分辨率较差.
DateTime能够存储多达一个刻度; 它是DateTime.Now没有使用那种准确性.
@MusiGenesis:我知道(现在)它是如何工作的,但是如果有这么精确的措施并不是那么精确,那对我来说似乎是误导.这就像是说我知道我的高度是纳米级的,而实际上我只是把它四舍五入到最近的一千万.

14> 小智..:

当您启动一个写入控制台的进程(使用System.Diagnostics),但您从未阅读过Console.Out流时,在输出一定量后,您的应用程序将显示为挂起.


重定向stdout和stderr并按顺序使用两个ReadToEnd调用时,仍然会发生同样的情况.为了安全地处理stdout和stderr,你必须为每个stdout和stderr创建一个读取线程.

15> Shaul says I..:

Linq-To-Sql中没有运算符快捷方式

看到这里.

简而言之,在Linq-To-Sql查询的条件子句中,您不能使用像||和那样的条件快捷方式&&来避免空引用异常; Linq-To-Sql评估OR或AND运算符的两侧,即使第一个条件不需要评估第二个条件!


TIL.BRB,重新优化了几百个LINQ查询......

16> BlueRaja - D..:

使用虚拟方法的默认参数

abstract class Base
{
    public virtual void foo(string s = "base") { Console.WriteLine("base " + s); }
}

class Derived : Base
{
    public override void foo(string s = "derived") { Console.WriteLine("derived " + s); }
}

...

Base b = new Derived();
b.foo();

输出:
派生基数


很奇怪,我觉得这很明显.如果声明的类型是`Base`,编译器应该从哪里获取默认值,如果不是`Base`?我认为如果声明的类型是*derived*类型,默认值可能会有所不同,即使名为(静态)的方法是基本方法.
@FredOverflow,我的问题是概念性的.虽然行为在实现中有意义,但它不直观且可能是错误的来源.恕我直言,C#编译器在覆盖时不应允许更改默认参数值.

17> Bjarke Ebert..:

可变集合中的值对象

struct Point { ... }
List mypoints = ...;

mypoints[i].x = 10;

没有效果.

mypoints[i]返回Point值对象的副本.C#happily允许您修改副本的字段.默默无闻.


更新: 这似乎是在C#3.0中修复的:

Cannot modify the return value of 'System.Collections.Generic.List.this[int]' because it is not a variable


我可以看出为什么这会让人感到困惑,因为它确实适用于数组(与你的答案相反),但不适用于其他动态集合,例如List .

18> BlueRaja - D..:

也许不是最糟糕的,但是.net框架的某些部分使用度,而其他部分使用弧度 (并且Intellisense出现的文档从不告诉您哪个,您必须访问MSDN才能找到)

所有这一切都可以通过一个Angle班来代替......



19> Matt Davis..:

对于C/C++程序员来说,过渡到C#是很自然的.然而,我遇到的最大问题(并且已经与其他人进行了相同的转换)并没有完全理解C#中的类和结构之间的区别.

在C++中,类和结构是相同的; 它们仅在默认可见性方面有所不同,其中类默认为私有可见性,而结构默认为公共可见性.在C++中,这个类定义

    class A
    {
    public:
        int i;
    };

在功能上等同于此结构定义.

    struct A
    {
        int i;
    };

但是,在C#中,类是引用类型,而结构是值类型.这在(1)决定何时使用一个而不是另一个,(2)测试对象相等,(3)性能(例如,装箱/拆箱)等方面产生了巨大的差异.

网上有各种与两者之间的差异有关的信息(例如,这里).我强烈鼓励任何过渡到C#的人至少掌握差异及其含义的实际知识.


那么,最糟糕的问题是人们在使用它之前没有花时间学习语言吗?
@ BlueRaja-DannyPflughoeft更像是显然类似语言的经典问题 - 他们使用非常相似的关键词,在许多情况下使用语法,但工作方式有很多不同.

20> Jeff Kotula..:

垃圾收集和Dispose().虽然您不必做任何事情来释放内存,但您仍然需要通过Dispose()释放资源.当您使用WinForms或以任何方式跟踪对象时,这是一个非常容易忘记的事情.


我认为关注的是*正确实施*IDisposable.
另一方面,using()习惯会让你意外地咬人,就像使用PInvoke时一样.您不希望处置API仍在引用的内容.
正确实现IDisposable是非常难以理解的,即使是我在此(.NET Framework指南)上找到的最佳建议也可能令人困惑,直到你最终"得到它".
using()块巧妙地解决了这个问题.每当您看到对Dispose的调用时,您都可以立即安全地重构使用().

21> Brian J Card..:

foreach循环变量范围!

var l = new List>();
var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" };
foreach (var s in strings)
{
    l.Add(() => s);
}

foreach (var a in l)
    Console.WriteLine(a());

打印五个"amet",而以下示例工作正常

var l = new List>();
var strings = new[] { "Lorem" , "ipsum", "dolor", "sit", "amet" };
foreach (var s in strings)
{
    var t = s;
    l.Add(() => t);
}

foreach (var a in l)
    Console.WriteLine(a());


这基本上等同于Jon的匿名方法示例.
除了foreach之外,"s"变量更容易与范围变量混合,这更加令人困惑.对于常见的for循环,索引变量显然与每次迭代的变量相同.
http://blogs.msdn.com/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx和是的,希望变量的范围"正确".
这是[在C#5中修复](http://stackoverflow.com/a/8899347/958732).

22> Shaul says I..:

MS SQL Server无法处理1753年之前的日期.重要的是,这与.NET DateTime.MinDate常量(1/1/1 )不同步.因此,如果你试图保存一个思想,一个畸形的日期(最近发生在我的数据导入中)或者仅仅是征服者威廉的出生日期,你就会遇到麻烦.没有内置的解决方法; 如果您可能需要在1753年之前使用日期,则需要编写自己的解决方法.


坦率地说,我认为MS SQL Server有这个权利而.Net是错误的.如果您进行研究,那么您就会知道1751之前的日期由于日历更改,完全跳过日期等而变得时髦.大多数RDBM都有一些截止点.这应该给你一个起点:http://www.ancestry.com/learn/library/article.aspx?article = 3358
此外,日期是1753 ..这几乎是我们第一次有连续日历没有日期被跳过.SQL 2008引入了Date和datetime2 datetype,它可以接受从1/1/01到12/31/9999的日期.但是,如果您真的要比较1753年之前的日期,那么使用这些类型的日期比较应该被怀疑.
通过维基百科在朱利安日你可以找到一个13行基本程序CALJD.BAS发布于1984年,可以将日期计算回到公元前5000年左右,考虑到闰日和1753年的跳过天数.所以我不明白为什么"现代"像SQL2008这样的系统应该做得更糟.您可能对15世纪的正确日期表示不感兴趣,但其他人可能会这样做,我们的软件应该没有错误处理.另一个问题是闰秒...

23> Shaul says I..:

令人讨厌的Linq Caching Gotcha

见我的问题,导致这一发现,并在博客谁发现了这个问题.

简而言之,DataContext保留了您加载的所有Linq-to-Sql对象的缓存.如果其他人对您之前加载的记录进行了任何更改,即使您明确重新加载记录,无法获取最新数据!

这是因为ObjectTrackingEnabledDataContext上调用了一个属性,默认情况下为true.如果将该属性设置为false,则每次都会重新加载记录... 但是 ......您不能使用SubmitChanges()持久保存对该记录的任何更改.

GOTCHA!



24> Stefan Stein..:

数组实现 IList

但是不要实现它.当您调用Add时,它会告诉您它不起作用.那么为什么一个类在它不能支持时实现一个接口呢?

编译,但不起作用:

IList myList = new int[] { 1, 2, 4 };
myList.Add(5);

我们有很多这个问题,因为序列化程序(WCF)将所有IList转换为数组并且我们得到运行时错误.


恕我直言,问题是微软没有为集合定义足够的接口.恕我直言,它应该有iEnumerable,iMultipassEnumerable(支持重置,并保证多次传递将匹配),iLiveEnumerable(如果集合在枚举期间发生变化,则会有部分定义的语义 - 更改可能会也可能不会出现在枚举中,但不应该导致伪造的结果或例外),iReadIndexable,iReadWriteIndexable等.因为接口可以"继承"其他接口,所以如果有的话,这将不会增加额外的工作(它会保存NotImplemented stubs).

25> Roman Starko..:

关于Stream.Read的合同是我见过很多人的事情:

// Read 8 bytes and turn them into a ulong
byte[] data = new byte[8];
stream.Read(data, 0, 8); // <-- WRONG!
ulong data = BitConverter.ToUInt64(data);

这是错误的原因是,最多只Stream.Read读取指定的字节数,但完全可以自由读取1个字节,即使在流结束前有另外7个字节可用.

它看起来如此相似并没有帮助,如果它没有异常返回Stream.Write,保证写入所有字节.上述代码几乎一直在工作也没有帮助.当然,没有现成的,方便的方法正确读取N个字节也无济于事.

因此,为了堵塞漏洞,并提高对此的认识,这里有一个正确的方法来做到这一点:

    /// 
    /// Attempts to fill the buffer with the specified number of bytes from the
    /// stream. If there are fewer bytes left in the stream than requested then
    /// all available bytes will be read into the buffer.
    /// 
    /// Stream to read from.
    /// Buffer to write the bytes to.
    /// Offset at which to write the first byte read from
    ///                      the stream.
    /// Number of bytes to read from the stream.
    /// Number of bytes read from the stream into buffer. This may be
    ///          less than requested, but only if the stream ended before the
    ///          required number of bytes were read.
    public static int FillBuffer(this Stream stream,
                                 byte[] buffer, int offset, int length)
    {
        int totalRead = 0;
        while (length > 0)
        {
            var read = stream.Read(buffer, offset, length);
            if (read == 0)
                return totalRead;
            offset += read;
            length -= read;
            totalRead += read;
        }
        return totalRead;
    }

    /// 
    /// Attempts to read the specified number of bytes from the stream. If
    /// there are fewer bytes left before the end of the stream, a shorter
    /// (possibly empty) array is returned.
    /// 
    /// Stream to read from.
    /// Number of bytes to read from the stream.
    public static byte[] Read(this Stream stream, int length)
    {
        byte[] buf = new byte[length];
        int read = stream.FillBuffer(buf, 0, length);
        if (read < length)
            Array.Resize(ref buf, read);
        return buf;
    }



26> softveda..:

今天我修了一个很长时间没收的错误.该错误位于多线程场景中使用的泛型类中,并且使用静态int字段使用Interlocked提供无锁同步.该错误是由于类型的泛型类的每个实例化都有自己的静态引起的.因此每个线程都有自己的静态字段,并没有按预期使用锁.

class SomeGeneric
{
    public static int i = 0;
}

class Test
{
    public static void main(string[] args)
    {
        SomeGeneric.i = 5;
        SomeGeneric.i = 10;
        Console.WriteLine(SomeGeneric.i);
        Console.WriteLine(SomeGeneric.i);
        Console.WriteLine(SomeGeneric.i);
    }
}

这打印5 10 5


很奇怪,我觉得这很明显.如果`i`的类型为'T`,请考虑它应该做什么.
您可以拥有一个非泛型基类,它定义静态,并从中继承泛型.虽然我从来没有因为C#中的这种行为而感到沮丧 - 我仍然记得一些C++模板的长时间调试...... Eww!:)

27> Stefan Stein..:

活动

我从未理解为什么事件是一种语言特征.它们使用起来很复杂:你需要在调用之前检查null,你需要取消注册(你自己),你无法找到谁注册了(例如:我注册了吗?).为什么事件不是图书馆中的一个类?基本上是专业的List吗?


我不喜欢.net事件的实现.应该通过调用添加订阅的方法来处理事件订阅,并返回IDisposable,当Dispose'd时,它将删除订阅.不需要组合"添加"和"删除"方法的特殊构造,其语义可能有点狡猾,特别是如果尝试添加并稍后删除多播委托(例如,添加"B"后跟"AB",则删除"B"(留下"BA")和"AB"(仍然留下"BA").哎呀.

28> chakrit..:

可以不止一次地评估枚举数

当你有一个懒惰枚举的可枚举并且你迭代它两次并得到不同的结果时它会咬你.(或者你得到相同的结果,但它不必要地执行两次)

例如,在编写某个测试时,我需要一些临时文件来测试逻辑:

var files = Enumerable.Range(0, 5)
    .Select(i => Path.GetTempFileName());

foreach (var file in files)
    File.WriteAllText(file, "HELLO WORLD!");

/* ... many lines of codes later ... */

foreach (var file in files)
    File.Delete(file);

想象一下File.Delete(file)投掷时的惊喜FileNotFound!!

这里发生的是,files枚举得到重复两次(从第一次迭代的结果根本记得),并在每个新的迭代你会被重新调用Path.GetTempFilename(),所以你会得到一组不同的临时文件名.

当然,解决方案是通过使用ToArray()或者急切枚举值ToList():

var files = Enumerable.Range(0, 5)
    .Select(i => Path.GetTempFileName())
    .ToArray();

当你做多线程的事情时,这甚至更可怕,例如:

foreach (var file in files)
    content = content + File.ReadAllText(file);

content.Length所有的写作后你发现仍然是0!然后你开始严格检查你没有竞争条件......在一个浪费时间之后......你发现它只是那个小小的可疑的东西你忘记了......



29> DevDave..:

刚刚发现了一个让我陷入调试一段时间的奇怪的事情:

您可以为可空int添加null而不抛出异常,并且值保持为null.

int? i = null;
i++; // I would have expected an exception but runs fine and stays as null



30> BlueRaja - D..:
TextInfo textInfo = Thread.CurrentThread.CurrentCulture.TextInfo;

textInfo.ToTitleCase("hello world!"); //Returns "Hello World!"
textInfo.ToTitleCase("hElLo WoRld!"); //Returns "Hello World!"
textInfo.ToTitleCase("Hello World!"); //Returns "Hello World!"
textInfo.ToTitleCase("HELLO WORLD!"); //Returns "HELLO WORLD!"

是的,记录了这种行为,但这肯定不是正确的.


我不同意 - 当一个单词全部上限时,它可以具有特殊含义,你不想搞乱Title Case,例如"美国总统" - >"美国总统",而不是"美国总统"美国".
@Shaul:在这种情况下,他们应该将其指定为[参数](https://connect.microsoft.com/VisualStudio/feedback/details/546435/textinfo-totitlecase-simple-fix-which-doesnt-break-backwards - 兼容性)以避免混淆,因为我从来没有见过任何提前预期这种行为的人 - 这使得它成为了***!

31> MikeJ..:

有一本关于.NET Gotchas的书

我最喜欢的是你在C#中创建一个类,将它继承到VB,然后尝试重新继承回C#,它不起作用.ARGGH


我发现这不是一个有用的功能!

32> Benjol..:

字典<,>:"未定义项目的返回顺序".这太可怕了,因为它有时可能会咬你,但是工作别人,如果你只是盲目地认为词典会发挥得很好("为什么不应该呢?我想,List会这样做"),你真的需要在你最终开始质疑你的假设之前,请先了解它.

(这里有类似的问题.)


当然List 很好玩.如果项目的顺序对您很重要,请使用SortedDictionary .
@ck:错了.List .GetEnumerator**确实**保留顺序.

33> Trident D'Ga..:

这是一个超级陷阱,我浪费了2天的故障排除.它没有抛出任何异常,它只是用一些奇怪的错误消息崩溃了web服务器.我无法在DEV中重现该问题.此外,项目构建设置的实验以某种方式使它在PROD中消失,然后又回来了.终于我明白了.

如果您在以下代码中看到问题,请告诉我:

private void DumpError(Exception exception, Stack context)
{
    if (context.Any())
    {
        Trace.WriteLine(context.Pop());
        Trace.Indent();
        this.DumpError(exception, context);
        Trace.Unindent();
    }
    else
    {
        Trace.WriteLine(exception.Message);
    }
}

所以,如果你重视自己的理智:

!从来没有给Trace方法添加任何逻辑!

代码必须如下所示:

private void DumpError(Exception exception, Stack context)
{
    if (context.Any())
    {
        var popped = context.Pop();
        Trace.WriteLine(popped);
        Trace.Indent();
        this.DumpError(exception, context);
        Trace.Unindent();
    }
    else
    {
        Trace.WriteLine(exception.Message);
    }
}



34> Jon B..:

MemoryStream.GetBuffer()VS MemoryStream.ToArray().前者返回整个缓冲区,后者只是使用过的部分.呸.


@MusiGenesis,使用(Stream stream = new ...){}.

35> BlueRaja - D..:

所有UserControls中的DesignMode属性实际上并不会告诉您是否处于设计模式.



36> Will Vousden..:

base在调试环境中计算时,关键字无法按预期工作:方法调用仍使用虚拟调度.

当我偶然发现它时,我浪费了很多时间,我以为我在CLR的时空中遇到了某种裂痕,但后来我意识到这是一个已知的(甚至有些故意的)错误:

http://blogs.msdn.com/jmstall/archive/2006/06/29/funceval-does-virtual-dispatch.aspx



37> Boris Lipsch..:

静态构造函数在锁定下执行.因此,从静态构造函数调用线程代码可能会导致死锁.这是一个演示它的示例:

using System.Threading;
class Blah
{
    static void Main() { /* Won’t run because the static constructor deadlocks. */ }

    static Blah()
    {
        Thread thread = new Thread(ThreadBody);
        thread.Start();
        thread.Join();
    }

    static void ThreadBody() { }
}



38> cciotti..:

如果您正在编写MOSS并且以这种方式获得站点引用:

SPSite oSiteCollection = SPContext.Current.Site;

然后在你的代码中你说:

oSiteCollection.Dispose();

来自MSDN:

如果创建SPSite对象,则可以使用Dispose方法关闭对象.但是,如果您具有对共享资源的引用,例如,当GetContextSite方法或Site属性(例如,SPContext.Current.Site)提供对象时,请不要使用Dispose方法关闭对象,而是允许Windows SharePoint Services或门户应用程序管理该对象.有关对象处理的详细信息,请参阅最佳实践:使用一次性Windows SharePoint Services对象.

这种情况发生在每个MOSS程序员和某些方面.



39> Shaul says I..:

ASP.NET:

如果您正在使用Linq-To-SQL,则调用SubmitChanges()数据上下文并抛出异常(例如重复键或其他约束违规),在调试时,有问题的对象值会保留在您的内存中,并且每次都会重新提交你随后打电话SubmitChanges().

现在这里是真正的踢球者:即使你按下IDE中的"停止"按钮并重新启动,坏值也将保留在内存中!我不明白为什么有人认为这是个好主意 - 但是系统托盘中弹出的那个小的ASP.NET图标仍然在运行,它似乎可以保存你的对象缓存.如果要刷新内存空间,则必须右键单击该图标并强制关闭它!GOTCHA!



40> Mahdi Tahsil..:
enum Seasons
{
    Spring = 1, Summer = 2, Automn = 3, Winter = 4
}

public string HowYouFeelAbout(Seasons season)
{
    switch (season)
    {
        case Seasons.Spring:
            return "Nice.";
        case Seasons.Summer:
            return "Hot.";
        case Seasons.Automn:
            return "Cool.";
        case Seasons.Winter:
            return "Chilly.";
    }
}

错误?
并非所有代码路径都返回一个值...
你在开玩笑吗?我敢打赌所有代码路径都返回一个值,因为Seasons这里提到了每个成员.它本应该检查所有枚举成员,如果一个成员在交换机情况下不存在,那么这样的错误将是有意义的,但是现在我应该添加一个Default冗余且永远不会被代码到达的情况.

编辑:
经过对此问题的更多研究后,我来到了埃里克·利珀特(Eric Lippert)那篇精彩的书面和有用的帖子,但它仍然有点奇怪.你同意吗?



41> jcollum..:

我经常要提醒自己,DateTime是一个值类型,而不是ref类型.对我来说似乎太奇怪了,特别是考虑到它的各种构造函数.


这为什么重要?无论如何,DateTime是不可变的,我看不出你确实需要知道它是否是引用类型的情况.
我经常一直输入小写的日期时间...幸运的是intellisense为我修复了:-)

42> GWLlosa..:

到目前为止,我最糟糕的一个,我今天才知道...如果你覆盖object.Equals(object obj),你可以发现:

((MyObject)obj).Equals(this);

表现不一样:

((MyObject)obj) == this;

一个会调用你的覆盖函数,另一个不会.


您可以覆盖==运算符,但覆盖.Equals()将不会为您执行此操作.所以你可以假设覆盖.Equals()和==,让他们做不同的事情:\
你可以**覆盖**等于和**重载**==.差异很微妙但非常重要.更多信息在这里.http://stackoverflow.com/questions/1766492/c-overloading-operator-versus-equals/1849288#1849288

43> BlueRaja - D..:

对于LINQ-to-SQL和LINQ-to-Entities

return result = from o in table
                where o.column == null
                select o;
//Returns all rows where column is null

int? myNullInt = null;
return result = from o in table
                where o.column == myNullInt
                select o;
//Never returns anything!

有对LINQ到entites的一个错误报告在这里,但似乎他们不经常检查该论坛.也许有人应该为LINQ-to-SQL提交一个?


对于那些因遇到这个错误而找到此页面的人,可以找到一个解决方法[这里](http://stackoverflow.com/questions/682429#2541042)

44> Shaul says I..:

必须按顺序添加Oracle参数

这是Oracle参数化查询的ODP .Net实现中的一个主要问题.

向查询添加参数时,默认行为是忽略参数名称,并按照添加顺序使用这些值.

解决方案是BindByNameOracleCommand对象的属性设置为true- false默认情况下......这是定性的(如果不是非常定量的)类似于DropDatabaseOnQueryExecution使用默认值调用的属性true.

他们称之为功能 ; 我把它称为公共领域的一个坑.

有关详细信息,请参见此处



45> Roboblob..:

看看这个:

class Program
{
    static void Main(string[] args)
    {
        var originalNumbers = new List { 1, 2, 3, 4, 5, 6 };

        var list = new List(originalNumbers);
        var collection = new Collection(originalNumbers);

        originalNumbers.RemoveAt(0);

        DisplayItems(list, "List items: ");
        DisplayItems(collection, "Collection items: ");

        Console.ReadLine();
    }

    private static void DisplayItems(IEnumerable items, string title)
    {
        Console.WriteLine(title);
        foreach (var item in items)
            Console.Write(item);
        Console.WriteLine();
    }
}

输出为:

List items: 123456
Collection items: 23456

接受IList的Collection构造函数在原始List周围创建包装器,而List构造函数创建新的List并将所有引用从原始副本复制到新List。

在此处查看更多信息:http : //blog.roboblob.com/2012/09/19/dot-net-gotcha-nr1-list-versus-collection-constructor/

推荐阅读
家具销售_903
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有