在c#或。net中最糟糕的问题是什么?

我最近正在使用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#陷阱分类会很有用。

39619 次浏览

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

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

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

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

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

类型。方法

我看过咬了很多人的是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<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

解决方案?

  • 比较字符串类型时总是使用string.Equals(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!
}

时间间隔。秒是时间跨度的秒部分(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:

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

每个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陷阱的书

我最喜欢的是你在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);
}
< p > 对于评论(和反对票): < br > 如此明显的堆栈溢出是极其罕见的。但是,如果发生异常,您将不会捕获异常,并且可能会花费几个小时试图查找问题的确切位置。如果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);
}
}
< p >打印 5 10 5 < / p >

发生在我身上最糟糕的事情是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()时都会重新提交。

我不明白为什么有人认为这是一个好主意——但是那个小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");

该对象将"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时我的惊讶吧!!

这里发生的事情是,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 !!然后,当....时,您开始严格检查是否没有竞态条件浪费了一个小时之后……你发现这只是一个微小的可枚举的东西,你忘记了....

相关对象和外键不同步

微软已经承认了这个漏洞

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

答案是您的IDE使它获得一个值,因为您打开了一个“监视”窗口——特别是“Autos”监视窗口,该窗口显示与当前或前一行执行相关的所有变量/属性的值。当你在第3行上碰到断点时,观察窗口认为你有兴趣知道MyObj的值——所以在幕后,忽略任何断点,它为你计算了MyObj的值——包括调用CreateMyObj()来设置_myObj!

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

明白了!


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

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

LinqToSQL和空集聚合

看到这个问题

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

例:假设你有一个表Claim和一个字段Amount,在LinqToSql中是类型decimal

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类来避免…

当Visible发生变化时,VisibleChanged通常不叫

如果你处于设计模式,所有UserControls中的DesignMode属性会执行我不会告诉你

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

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

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

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

问题来了(和回答),在那里我探索了这个问题。

简而言之,如果你试图在调用DataContext.SubmitChanges()之前在内存中构建太多的对象,你就会开始以几何速率经历延迟。我还没有100%确认情况是这样的,但在我看来,对DataContext.GetChangeSet()的调用会导致数据上下文对更改集中2项的每一个组合执行等价性评估(.Equals()),可能是为了确保它不会重复插入或导致其他并发问题。问题是,如果你有非常大的批,比较的数量与n的平方成比例地增加,即(n ^ 2 + n) / 2。1000个条目在内存中意味着超过50万次比较……这可能要花很长时间。

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

在虚方法中使用默认参数

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();
< br > < p >输出: 派生基地< / p >

数组实现IList

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

编译,但不工作:

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

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

事件

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

递归属性有问题

我认为不是c#特有的,我确信我在SO的其他地方看到过它(是提醒我的问题)

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

重写属性时忘记引用base.:

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

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

它看起来与Stream.Write如此相似,这并没有帮助,如果它没有异常返回,保证已经写入了所有的字节。上面的代码几乎所有时间都有效. c也没有帮助。当然,没有现成的、方便的方法来正确地读取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;
}

我一直认为value类型总是在stack上,而reference类型总是在heap上。

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

作为Jon双向飞碟回答(给出Eric Lippert的博客文章的引用),它是神话

相当重要的环节:

关于值类型的真相

引用不是aAddress

The Stack is an Implementation Detail Part 1

The Stack is an Implementation Detail Part 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/ < / p >

这是一个超级陷阱,我浪费了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.";
}
}
< p >错误呢?< br > 不是所有的代码路径都返回一个值… < br > 你在开玩笑吗?我打赌所有的代码路径都返回一个值,因为这里提到了每个Seasons成员。它应该检查所有枚举成员,如果在开关情况下成员不存在,那么这样的错误将是有意义的,但现在我应该添加一个Default情况,这是冗余的,并且永远不会被代码到达。< / p > < p > 编辑: < br > 在对这个Gotcha进行了更多的研究之后,我找到了Eric Lippert写得很好,很有用,但它仍然有点奇怪。你同意吗?< / p >

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

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

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

对于一个可为空的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...