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

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

AddDays()的智能感知文档说它在日期后添加了一天,但它并没有这样做——它实际上返回了一个添加了一天的日期,所以你必须这样写:

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

这个问题以前已经困扰过我很多次了,所以我认为将最糟糕的c#陷阱分类会很有用。


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

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

因为这将返回0,而不是大多数人期望的1


mystring.Replace("x","y")

虽然它看起来应该对被调用的字符串进行替换,但实际上它返回了一个新字符串,替换完成后没有改变被调用的字符串。你需要记住字符串是不可变的。


类型。方法

我见过的咬了很多人的是Type.GetType(string)。他们想知道为什么它适用于自己程序集中的类型,以及一些类型,如System。字符串,而不是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<char> 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
        }
    }
}

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

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


重载==操作符和非类型化容器(数组列表、数据集等):

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

解决方案?

总是使用字符串。当比较字符串类型时等于(a, b) 使用像List<string>这样的泛型来确保两个操作数都是字符串。


这是另一个让我困惑的问题:

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

时间间隔。Seconds是时间跨度的秒部分(2分钟和0秒的秒值为0)。

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


private int myVar;
public int MyVar
{
    get { return MyVar; }
}

心想。你的应用在没有堆栈跟踪的情况下崩溃了。这种事经常发生。

(请注意getter中的大写MyVar而不是小写MyVar。)


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


一些代码:

        List<int> a = new List<int>();
        for (int i = 0; i < 10; i++)
        {
            a.Add(i);
        }

        var q1 = (from aa in a
                  where aa == 2
                  select aa).Single();

        var q2 = (from aa in a
                  where aa == 2
                  select aa).First();

q1 -在这个查询中检查List中的所有整数; Q2 -检查整数,直到找到“正确的”整数。


如果你正在为MOSS编写代码,你以这种方式获得一个站点引用:

SPSite oSiteCollection = SPContext.Current.Site;

之后在你的代码中你说:

oSiteCollection.Dispose();

从MSDN:

If you create an SPSite object, you can use the Dispose method to close the object. However, if you have a reference to a shared resource, such as when the object is provided by the GetContextSite method or Site property (for example, SPContext.Current.Site), do not use the Dispose method to close the object, but instead allow Windows SharePoint Services or your portal application to manage the object. For more information about object disposal, see Best Practices: Using Disposable Windows SharePoint Services Objects.

每个MOSS程序员都会遇到这种情况。


当您启动一个进程(使用System.Diagnostics)写入控制台,但从未读取控制台时。输出流,在一定数量的输出后,你的应用程序将出现挂起。


Foreach循环变量范围!

var l = new List<Func<string>>();
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<Func<string>>();
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());

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


有一本关于。net gottchas的书

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


可变集合中的值对象

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

mypoints[i].x = 10;

没有效果。

mypoints[i]返回一个Point值对象的副本。c#允许您修改副本的字段。默默地什么也不做。


更新: 这似乎在c# 3.0中被修复了:

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

如果算上ASP。NET,我得说webforms的生命周期对我来说是一个相当大的难题。我花了无数的时间调试写得很糟糕的webforms代码,只是因为很多开发人员真的不明白什么时候使用哪个事件处理程序(很遗憾,包括我在内)。


抛出收到异常

一个让许多新开发人员困惑的问题是重新抛出异常语义。

我经常看到如下代码

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

问题是它会清除堆栈跟踪,并使诊断问题变得更加困难,因为您无法跟踪异常起源于何处。

正确的代码是不带参数的throw语句:

catch(Exception)
{
    throw;
}

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

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

下面的代码不会捕获. net中的异常。相反,它会导致StackOverflow异常。

private void button1_Click( object sender, EventArgs e ) {
    try {
        CallMe(234);
    } catch (Exception ex) {
        label1.Text = ex.Message.ToString();
    }
}
private void CallMe( Int32 x ) {
    CallMe(x);
}

对于评论(和反对票): 如此明显的堆栈溢出是极其罕见的。但是,如果发生异常,您将不会捕获异常,并且可能会花费几个小时试图查找问题的确切位置。如果SO出现在很少使用的逻辑路径中,特别是在web应用程序中,你可能不知道引发问题的确切条件,那么情况就会更加复杂。

这与这个问题的公认答案完全相同(https://stackoverflow.com/a/241194/2424)。答案上的属性getter本质上做的事情与上面的代码完全相同,并且在没有堆栈跟踪的情况下崩溃。


MemoryStream.GetBuffer() vs MemoryStream.ToArray()。前者返回整个缓冲区,后者只返回使用的部分。讨厌的东西。


到目前为止我最糟糕的一个,我今天才想出来……如果你重写object。Equals(object obj),你会发现:

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

与以下行为不同:

((MyObject)obj) == this;

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


对于C/ c++程序员来说,过渡到c#是很自然的事情。然而,我个人遇到的最大的问题(以及其他人在做同样的转换时遇到的问题)是没有完全理解c#中类和结构之间的区别。

在c++中,类和结构是相同的;它们只在默认可见性上有所不同,其中类默认为私有可见性,结构默认为公共可见性。在c++中,这个类定义

    class A
    {
    public:
        int i;
    };

在功能上等价于此结构定义。

    struct A
    {
        int i;
    };

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

网络上有各种各样的信息与两者之间的差异有关(例如,这里)。我强烈建议任何想要过渡到c#的人至少要了解这些差异及其含义。


Dictionary<,>: "返回项的顺序未定义"。这很可怕,因为它有时会咬你一口,但会对其他人起作用,如果你只是盲目地认为Dictionary会表现得很好(“为什么不呢?”我想,是List做的”),在你最终开始质疑你的假设之前,你真的必须深入了解它。

(这里也有类似问题。)


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。

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

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

哎呀!


今天我修复了一个长时间未能解决的bug。该错误存在于一个用于多线程场景的泛型类中,并且使用一个静态int字段来使用Interlocked提供无锁同步。该错误是因为类型的泛型类的每个实例化都有自己的静态。因此,每个线程都有自己的静态字段,并没有像预期的那样使用锁。

class SomeGeneric<T>
{
    public static int i = 0;
}

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

这个打印 5 10 5


发生在我身上最糟糕的事情是webBrowser documentText问题:

Link

AllowNavigation解决方案工作在Windows窗体…

但在紧凑框架中,这种属性不存在……

...到目前为止,我找到的唯一解决办法是重建浏览器控件:

http://social.msdn.microsoft.com/Forums/it-IT/netfxcompact/thread/5637037f-96fa-48e7-8ddb-6d4b1e9d7db9

但是这样做,你需要处理手头的浏览器历史…: P


我来这个派对有点晚了,但我最近有两个问题都困扰着我:

DateTime决议

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。毫秒,您将得到以15.625毫秒为单位的四舍五入块的值:15、31、46等等。

这种特殊的行为因系统而异,但是在这个日期/时间API中还有其他与解析相关的问题。


路径。结合

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

如果第二个参数以\字符开头,它不会给你一个完整的路径:

这段代码:

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\

[Serializable]
class Hello
{
    readonly object accountsLock = new object();
}

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

这个故事的寓意:反序列化对象时不运行字段初始化程序


ASP。NET:

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

现在真正的问题是:即使您在IDE中按下“停止”按钮并重新启动,坏值仍将保留在内存中!我不明白为什么有人认为这是一个好主意-但小ASP。. NET图标弹出在您的系统托盘保持运行,它似乎保存您的对象缓存。如果你想冲洗你的内存空间,你必须右键单击该图标并强制关闭它!明白了!


MS SQL Server不能处理1753年以前的日期。值得注意的是,这与. net DateTime不同步。MinDate常数,也就是1/1/1。因此,如果你试图保存一个错误的日期(就像我最近在数据导入中遇到的那样),或者只是征服者威廉的出生日期,你就会遇到麻烦。没有内置的解决方案;如果你可能需要使用1753年之前的日期,你需要编写自己的变通方案。


LINQ to SQL和一对多关系

这是一个可爱的,咬了我几次,微软把它留给他们自己的开发人员放在她的博客。我不能说得比她更好,来看看。


Linq-To-Sql和数据库/本地代码歧义

有时Linq无法判断某个方法是要在DB上执行还是在本地代码中执行。

请看这里和这里的问题陈述和解决方案。


内存泄漏,因为您没有取消关联事件。

这甚至让我认识的一些高级开发人员被发现了。

想象一个WPF表单,里面有很多东西,你在其中的某个地方订阅了一个事件。如果您不取消订阅,那么关闭和取消引用后,整个表单将保留在内存中。

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

在本例中,您的拆卸代码应该具有

timer.Tick -= TimerTickEventHandler;

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


讨厌的Linq缓存抓到了你

看看我的问题导致了这个发现,以及发现这个问题的博主。

简而言之,DataContext保存了你曾经加载过的所有Linq-to-Sql对象的缓存。如果其他人对您之前加载的记录进行了任何更改,则您将无法获得最新数据,即使您显式地重新加载该记录!

这是因为DataContext上有一个名为ObjectTrackingEnabled的属性,默认为true。如果您将该属性设置为false,则记录将每次重新加载…但是…你不能使用SubmitChanges()持久化对该记录的任何更改。

明白了!


也许不是真的抓住你,因为行为写得很清楚在MSDN中,但已经打破了我的脖子,因为我发现它相当反直觉:

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

这家伙留下了“不错”。图片“文件锁定,直到图像被处置。在我面对它的时候,我认为它会很好地加载图标,并没有意识到(一开始),我最终有几十个打开和锁定的文件!Image一直跟踪它从哪里加载文件…

如何解决这个问题?我以为只用一行就行了。我期望FromFile()有一个额外的参数,但没有,所以我写了这个…

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

在Linq-To-Sql中没有操作符快捷方式

在这里看到的。

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


可枚举对象可以计算不止一次

当您有一个惰性枚举的可枚举对象,并且对其进行两次迭代并得到不同的结果时,它会咬您一口。(或者你得到相同的结果,但它执行两次不必要的)

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

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时我的惊讶!!

这里发生的情况是,可枚举的文件被迭代了两次(第一次迭代的结果根本没有被记住),在每次新的迭代中,您将重新调用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);

你会发现内容。在所有写入之后,长度仍然为0 !!然后,当....时,您开始严格检查是否没有竞态条件浪费了一个小时之后……你发现这只是一个微小的可枚举的东西,你忘记了....


相关对象和外键不同步

微软已经承认了这个漏洞。

我有一个类Thing,它有一个FK到Category。Category与Thing之间没有定义的关系,以免污染接口。

var thing = CreateThing(); // does stuff to create a thing
var category = GetCategoryByID(123); // loads the Category with ID 123
thing.Category = category;
Console.WriteLine("Category ID: {0}", thing.CategoryID); 

输出:

Category ID: 0

类似的:

var thing = CreateThing();
thing.CategoryID = 123;
Console.WriteLine("Category name: {0}", order.Category.Name);

抛出NullReferenceException。相关对象Category不加载ID为123的Category记录。

但是,在向DB提交更改之后,这些值将得到同步。但是在您访问DB之前,FK值和相关对象的功能实际上是独立的!

(有趣的是,同步FK值与相关对象的失败似乎只发生在没有定义子关系的情况下,即Category没有“Things”属性。但当你只是设置FK值时,“按需加载”永远不会起作用。)

明白了!


海森堡表窗

如果你在做按需加载的事情,这会让你很难受,就像这样:

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显然是空的,对吗?嗯…所以…为什么它跳过条件直接返回_myObj?!将鼠标悬停在_myObj上…事实上,它确实有价值!怎么会这样?!

The answer is that your IDE caused it to get a value, because you have a "watch" window open - especially the "Autos" watch window, which displays the values of all variables/properties relevant to the current or previous line of execution. When you hit your breakpoint on Line 3, the watch window decided that you would be interested to know the value of MyObj - so behind the scenes, ignoring any of your breakpoints, it went and calculated the value of MyObj for you - including the call to CreateMyObj() that sets the value of _myObj!

这就是为什么我称其为海森堡观察窗口-你不能观察值而不影响它…:)

明白了!


编辑-我觉得@ChristianHayter的评论应该包含在主要答案中,因为它看起来是解决这个问题的有效方法。所以任何时候你有一个惰性加载的属性…

用[DebuggerBrowsable(DebuggerBrowsableState.Never)]或[DebuggerDisplay("<loaded on demand>")]装饰你的属性。——克里斯蒂安·海特


LinqToSQL和空集聚合

看这个问题。

如果你有一个LinqToSql查询,你正在运行一个聚合——如果你的结果集是空的,Linq不能计算出数据类型是什么,即使它已经声明了。

例:假设你有一个带有Amount字段的Claim表,它在LinqToSql中是十进制类型。

var sum = Claims.Where(c => c.ID < 0).Sum(c => c.Amount);

显然,没有任何声明的ID小于零,因此应该看到sum = null,对吧?错了!您会得到一个InvalidOperationException,因为Linq查询底层的SQL查询没有数据类型。你必须明确地告诉Linq它是一个小数!因此:

var sum = Claims.Where(c => c.ID < 0).Sum(c => (decimal?)c.Amount);

这真的很愚蠢,在我看来,这是微软的设计错误。

明白了!


对于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-to-Entites的错误报告,尽管他们似乎不经常检查这个论坛。也许有人也应该为LINQ-to-SQL申请一个?


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!"

是的,这种行为是有记录的,但这并不能证明它是正确的。


也许不是最糟糕的,但是。net框架的某些部分使用角度,而其他部分使用弧度(智能感知出现的文档从来没有告诉你是哪个,你必须访问MSDN才能找到)

所有这些都可以通过使用一个Angle类来避免……


当可见改变时,通常不调用VisibleChanged。


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


在调试环境中求值时,base关键字不能按预期工作:方法调用仍然使用虚拟分派。

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

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


LinqToSql批处理速度随着批处理大小的平方而变慢

以下是我探索这个问题的问题(和答案)。

In a nutshell, if you try to build up too many objects in memory before calling DataContext.SubmitChanges(), you start experiencing sluggishness at a geometric rate. I have not confirmed 100% that this is the case, but it appears to me that the call to DataContext.GetChangeSet() causes the data context to perform an equivalence evaluation (.Equals()) on every single combination of 2 items in the change set, probably to make sure it's not double-inserting or causing other concurrency issues. Problem is that if you have very large batches, the number of comparisons increases proportionately with the square of n, i.e. (n^2+n)/2. 1,000 items in memory means over 500,000 comparisons... and that can take a heckuva long time.

为了避免这种情况,您必须确保对于预计有大量项目的任何批处理,您都在事务边界内完成整个工作,在创建每个项目时保存它,而不是在最后进行一次大保存。


在虚方法中使用默认参数

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();

输出: 派生的基础


数组实现IList

但是不要执行它。当您调用Add时,它会告诉您它不起作用。那么,为什么一个类在不支持接口的情况下还要实现接口呢?

编译,但不工作:

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

我们经常遇到这个问题,因为序列化器(WCF)将所有的list转换为数组,我们会得到运行时错误。


事件

我一直不明白为什么事件是一种语言特性。它们使用起来很复杂:你需要在调用之前检查null,你需要取消注册(你自己),你不能找到谁注册了(例如:我注册了吗?)为什么事件不只是图书馆中的一个类呢?基本上是一个专门的List<delegate>?


递归属性有问题

我认为不是c#特有的,而且我确信我在SO的其他地方看到过它(这个问题让我想起了它)

它有两种发生方式,但最终结果是一样的:

忘记引用基数。当重写一个属性时:

 public override bool IsRecursive
 {
     get { return IsRecursive; }
     set { IsRecursive = value; }
 }

从auto-属性改为back -属性,但并没有完全改变:

public bool IsRecursive
{
    get { return IsRecursive; }
    set { IsRecursive = value; }
}

Oracle参数必须按顺序添加

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

在向查询中添加参数时,默认行为是忽略参数名,并按添加值的顺序使用值。

解决方案是将OracleCommand对象的BindByName属性设置为true -默认为false…这是定性的(如果不是定量的),就像有一个属性叫做DropDatabaseOnQueryExecution,默认值为true。

他们称之为特征;我称之为公共领域的坑。

请看这里了解更多细节。


Linq2SQL:接口成员的映射[…]不支持。

如果对实现接口的对象执行Linq2Sql查询,则会得到非常奇怪的行为。假设你有一个类MyClass,它实现了一个接口IHasDescription,这样:

public interface IHasDescription {
  string Description { get; set; }
}

public partial class MyClass : IHasDescription { }

(MyClass的另一半是一个Linq2Sql生成的类,包括属性Description。)

现在你写一些代码(这通常发生在泛型方法中):

public static T GetByDescription<T>(System.Data.Linq.Table<T> table, string desc) 
  where T : class, IHasDescription {
  return table.Where(t => t.Description == desc).FirstOrDefault();
}

编译正常-但你会得到一个运行时错误:

NotSupportedException: The mapping of interface member IHasDescription.Description is not supported.

现在该怎么办呢?好吧,这真的很明显:只需将==更改为.Equals(),这样:

return table.Where(t => t.Description.Equals(desc)).FirstOrDefault();

现在一切都好了!

在这里看到的。


合同在流。阅读是我见过很多人被绊倒的东西:

// 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);

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

它看起来与Stream如此相似,这并没有什么帮助。如果没有异常返回,则保证已写入所有字节。上面的代码几乎一直都能工作,这也没有什么帮助。当然,没有现成的、方便的方法来正确地读取N个字节也无济于事。

所以,为了堵住这个漏洞,提高人们的意识,这里有一个正确的方法:

    /// <summary>
    /// 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.
    /// </summary>
    /// <param name="stream">Stream to read from.</param>
    /// <param name="buffer">Buffer to write the bytes to.</param>
    /// <param name="offset">Offset at which to write the first byte read from
    ///                      the stream.</param>
    /// <param name="length">Number of bytes to read from the stream.</param>
    /// <returns>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.</returns>
    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;
    }

    /// <summary>
    /// 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.
    /// </summary>
    /// <param name="stream">Stream to read from.</param>
    /// <param name="length">Number of bytes to read from the stream.</param>
    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;
    }

我一直认为值类型总是在堆栈上,引用类型总是在堆上。

但事实并非如此。当我最近在SO上看到这个问题时(可以说回答是错误的),我才知道事实并非如此。

正如Jon Skeet的回答(引用Eric Lippert的博客文章),这是一个神话。

相当重要的环节:

关于值类型的真相

引用不是aAddress

堆栈是实现细节第1部分

堆栈是实现细节第2部分


静态构造函数在锁定状态下执行。因此,从静态构造函数调用线程代码可能会导致死锁。 下面是一个例子:

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() { }
}

看看这个:

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

        var list = new List<int>(originalNumbers);
        var collection = new Collection<int>(originalNumbers);

        originalNumbers.RemoveAt(0);

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

        Console.ReadLine();
    }

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

输出是:

List items: 123456
Collection items: 23456

接受IList的集合构造函数会对原始List创建一个包装器,而List构造函数会创建一个新List并将所有引用从原始List复制到新List。

点击这里查看更多信息: http://blog.roboblob.com/2012/09/19/dot-net-gotcha-nr1-list-versus-collection-constructor/


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

如果你在下面这段代码中发现了问题,请告诉我:

private void DumpError(Exception exception, Stack<String> 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<String> context)
{
    if (context.Any())
    {
        var popped = context.Pop();
        Trace.WriteLine(popped);
        Trace.Indent();
        this.DumpError(exception, context);
        Trace.Unindent();
    }
    else
    {
        Trace.WriteLine(exception.Message);
    }
}

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成员。它应该已经检查所有enum成员,如果一个成员在开关情况下是缺席的,那么这样的错误将是有意义的,但现在我应该添加一个默认情况下,这是冗余的,永远不会被代码达到。

编辑: 在对这个Gotcha进行了更多的研究之后,我看到了Eric Lippert写得很好的和有用的帖子,但它仍然有点奇怪。你同意吗?


有时堆栈跟踪中的行号与源代码中的行号不匹配。这可能是因为为了优化而内联简单(单行)函数。对于使用日志进行调试的人来说,这是一个严重的混淆来源。

编辑:示例:有时你会在堆栈跟踪中看到一个空引用异常,它指向一行绝对不可能出现空引用异常的代码,就像一个简单的整数赋值。


只是发现了一个奇怪的问题,让我在调试中困了一段时间:

对于一个可为空的int,可以增加null值而不抛出异常,并且该值保持为空。

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

不是最糟糕的,但还没被提起。工厂方法作为参数传递给System.Collections.Concurrent方法可以被多次调用,即使只使用了一个返回值。考虑到. net在线程原语中多么强烈地试图保护您不受虚假唤醒的影响,这可能会让您感到惊讶。

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ValueFactoryBehavingBadlyExample
{
    class Program
    {
        static ConcurrentDictionary<int, int> m_Dict = new ConcurrentDictionary<int, int>();
        static ManualResetEventSlim m_MRES = new ManualResetEventSlim(false);
        static void Main(string[] args)
        {
            for (int i = 0; i < 8; ++i)
            {
                Task.Factory.StartNew(ThreadGate, TaskCreationOptions.LongRunning);
            }
            Thread.Sleep(1000);
            m_MRES.Set();
            Thread.Sleep(1000);
            Console.WriteLine("Dictionary Size: " + m_Dict.Count);
            Console.Read();
        }

        static void ThreadGate()
        {
            m_MRES.Wait();
            int value = m_Dict.GetOrAdd(0, ValueFactory);
        }

        static int ValueFactory(int key)
        {
            Thread.Sleep(1000);
            Console.WriteLine("Value Factory Called");
            return key;
        }
    }
}

(可能)输出:

Value Factory Called
Value Factory Called
Value Factory Called
Value Factory Called
Dictionary Size: 0
Value Factory Called
Value Factory Called
Value Factory Called
Value Factory Called

将容量传递给List<int>,而不是使用集合初始化式。

var thisOnePasses = new List<int> {2}; // collection initializer
var thisOneFails = new List<int> (2);  // oops, use capacity by mistake #gotcha#

thisOnePasses.Count.Should().Be(1);
thisOnePasses.First().Should().Be(2);

thisOneFails.Count.Should().Be(1);     // it's zero
thisOneFails.First().Should().Be(2);   // Sequence contains no elements...