我最近在使用一个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#陷阱会很有用.
private int myVar; public int MyVar { get { return MyVar; } }
Blammo.您的应用程序崩溃,没有堆栈跟踪.一直发生.
(在getter中注意资本MyVar
而不是小写myVar
.)
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 IEnumerableCapitalLetters(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()
方法之前,代码源中的代码不会被执行.
我的脑筋急转弯页面上还有其他一些奇怪的东西.
获得大量新开发人员的问题是重新抛出异常语义.
很多时候我看到如下代码
catch(Exception e) { // Do stuff throw e; }
问题是它会擦除堆栈跟踪并使诊断问题更加困难,导致无法跟踪异常的来源.
正确的代码是没有args的throw语句:
catch(Exception) { throw; }
或者将异常包装在另一个异常中,并使用内部异常来获取原始堆栈跟踪:
catch(Exception e) { // Do stuff throw new MySpecialException(e); }
海森堡观察窗
如果您正在进行按需加载的操作,这可能会让您感到非常不满,例如:
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("
")]装饰属性. - 克里斯蒂安·海特
这是另一次让我:
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).
泄漏内存,因为您没有取消挂钩事件.
这甚至让我认识的一些高级开发人员感到惊讶
想象一下WPF表单中有很多东西,在那里你订阅了一个事件.如果您没有取消订阅,则在关闭和取消引用后,整个表单将保留在内存中.
我相信我看到的问题是在WPF表单中创建一个DispatchTimer并订阅Tick事件,如果你不做 - =在计时器上你的表单泄漏了内存!
在这个示例中,您的拆卸代码应具有
timer.Tick -= TimerTickEventHandler;
这个特别棘手,因为你在WPF表单中创建了DispatchTimer的实例,所以你会认为它将是垃圾收集过程处理的内部引用...不幸的是,DispatchTimer使用静态的内部订阅和服务列表UI线程上的请求,因此引用由静态类"拥有".
也许不是真的有问题,因为这种行为在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); }
如果算上ASP.NET,我会说webforms生命周期对我来说是一个非常大的问题.我花了无数个小时来调试写得不好的webforms代码,只是因为很多开发人员并不真正理解何时使用哪个事件处理程序(遗憾的是我包括在内).
重载==运算符和无类型容器(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
来确保两个操作数都是字符串.
[Serializable] class Hello { readonly object accountsLock = new object(); } //Do stuff to deserialize Hello with BinaryFormatter //and now... accountsLock == null ;)
故事的道德:反序列化对象时不会运行字段初始化器
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"
哎呦!
前几天我看到这张贴出来了,我觉得它很晦涩,对那些不知道的人来说很痛苦
int x = 0; x = x++; return x;
因为那将返回0而不是大多数人预期的1
我参加这个聚会有点晚了,但我最近有两个问题都咬了我:
日期时间分辨率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中还有其他与分辨率相关的问题.
组合文件路径的好方法,但它并不总是按照您期望的方式运行.
如果第二个参数以\
字符开头,则不会为您提供完整的路径:
这段代码:
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\
当您启动一个写入控制台的进程(使用System.Diagnostics),但您从未阅读过Console.Out流时,在输出一定量后,您的应用程序将显示为挂起.
Linq-To-Sql中没有运算符快捷方式
看到这里.
简而言之,在Linq-To-Sql查询的条件子句中,您不能使用像||
和那样的条件快捷方式&&
来避免空引用异常; Linq-To-Sql评估OR或AND运算符的两侧,即使第一个条件不需要评估第二个条件!
使用虚拟方法的默认参数
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();
输出:
派生基数
struct Point { ... } Listmypoints = ...; 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
也许不是最糟糕的,但是.net框架的某些部分使用度,而其他部分使用弧度 (并且Intellisense出现的文档从不告诉您哪个,您必须访问MSDN才能找到)
所有这一切都可以通过一个Angle
班来代替......
对于C/C++程序员来说,过渡到C#是很自然的.然而,我遇到的最大问题(并且已经与其他人进行了相同的转换)并没有完全理解C#中的类和结构之间的区别.
在C++中,类和结构是相同的; 它们仅在默认可见性方面有所不同,其中类默认为私有可见性,而结构默认为公共可见性.在C++中,这个类定义
class A { public: int i; };
在功能上等同于此结构定义.
struct A { int i; };
但是,在C#中,类是引用类型,而结构是值类型.这在(1)决定何时使用一个而不是另一个,(2)测试对象相等,(3)性能(例如,装箱/拆箱)等方面产生了巨大的差异.
网上有各种与两者之间的差异有关的信息(例如,这里).我强烈鼓励任何过渡到C#的人至少掌握差异及其含义的实际知识.
垃圾收集和Dispose().虽然您不必做任何事情来释放内存,但您仍然需要通过Dispose()释放资源.当您使用WinForms或以任何方式跟踪对象时,这是一个非常容易忘记的事情.
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());
MS SQL Server无法处理1753年之前的日期.重要的是,这与.NET DateTime.MinDate
常量(1/1/1 )不同步.因此,如果你试图保存一个思想,一个畸形的日期(最近发生在我的数据导入中)或者仅仅是征服者威廉的出生日期,你就会遇到麻烦.没有内置的解决方法; 如果您可能需要在1753年之前使用日期,则需要编写自己的解决方法.
令人讨厌的Linq Caching Gotcha
见我的问题,导致这一发现,并在博客谁发现了这个问题.
简而言之,DataContext保留了您加载的所有Linq-to-Sql对象的缓存.如果其他人对您之前加载的记录进行了任何更改,即使您明确重新加载记录,也无法获取最新数据!
这是因为ObjectTrackingEnabled
DataContext上调用了一个属性,默认情况下为true.如果将该属性设置为false,则每次都会重新加载记录... 但是 ......您不能使用SubmitChanges()持久保存对该记录的任何更改.
GOTCHA!
数组实现 IList
但是不要实现它.当您调用Add时,它会告诉您它不起作用.那么为什么一个类在它不能支持时实现一个接口呢?
编译,但不起作用:
IListmyList = new int[] { 1, 2, 4 }; myList.Add(5);
我们有很多这个问题,因为序列化程序(WCF)将所有IList转换为数组并且我们得到运行时错误.
关于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; }
今天我修了一个很长时间没收的错误.该错误位于多线程场景中使用的泛型类中,并且使用静态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
活动
我从未理解为什么事件是一种语言特征.它们使用起来很复杂:你需要在调用之前检查null,你需要取消注册(你自己),你无法找到谁注册了(例如:我注册了吗?).为什么事件不是图书馆中的一个类?基本上是专业的List
吗?
可以不止一次地评估枚举数
当你有一个懒惰枚举的可枚举并且你迭代它两次并得到不同的结果时它会咬你.(或者你得到相同的结果,但它不必要地执行两次)
例如,在编写某个测试时,我需要一些临时文件来测试逻辑:
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!然后你开始严格检查你没有竞争条件......在一个浪费时间之后......你发现它只是那个小小的可疑的东西你忘记了......
刚刚发现了一个让我陷入调试一段时间的奇怪的事情:
您可以为可空int添加null而不抛出异常,并且值保持为null.
int? i = null; i++; // I would have expected an exception but runs fine and stays as null
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 Gotchas的书
我最喜欢的是你在C#中创建一个类,将它继承到VB,然后尝试重新继承回C#,它不起作用.ARGGH
字典<,>:"未定义项目的返回顺序".这太可怕了,因为它有时可能会咬你,但是工作别人,如果你只是盲目地认为词典会发挥得很好("为什么不应该呢?我想,List会这样做"),你真的需要在你最终开始质疑你的假设之前,请先了解它.
(这里有类似的问题.)
这是一个超级陷阱,我浪费了2天的故障排除.它没有抛出任何异常,它只是用一些奇怪的错误消息崩溃了web服务器.我无法在DEV中重现该问题.此外,项目构建设置的实验以某种方式使它在PROD中消失,然后又回来了.终于我明白了.
如果您在以下代码中看到问题,请告诉我:
private void DumpError(Exception exception, Stackcontext) { 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, Stackcontext) { if (context.Any()) { var popped = context.Pop(); Trace.WriteLine(popped); Trace.Indent(); this.DumpError(exception, context); Trace.Unindent(); } else { Trace.WriteLine(exception.Message); } }
MemoryStream.GetBuffer()
VS MemoryStream.ToArray()
.前者返回整个缓冲区,后者只是使用过的部分.呸.
所有UserControls中的DesignMode属性实际上并不会告诉您是否处于设计模式.
base
在调试环境中计算时,关键字无法按预期工作:方法调用仍使用虚拟调度.
当我偶然发现它时,我浪费了很多时间,我以为我在CLR的时空中遇到了某种裂痕,但后来我意识到这是一个已知的(甚至有些故意的)错误:
http://blogs.msdn.com/jmstall/archive/2006/06/29/funceval-does-virtual-dispatch.aspx
静态构造函数在锁定下执行.因此,从静态构造函数调用线程代码可能会导致死锁.这是一个演示它的示例:
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() { } }
如果您正在编写MOSS并且以这种方式获得站点引用:
SPSite oSiteCollection = SPContext.Current.Site;
然后在你的代码中你说:
oSiteCollection.Dispose();
来自MSDN:
如果创建SPSite对象,则可以使用Dispose方法关闭对象.但是,如果您具有对共享资源的引用,例如,当GetContextSite方法或Site属性(例如,SPContext.Current.Site)提供对象时,请不要使用Dispose方法关闭对象,而是允许Windows SharePoint Services或门户应用程序管理该对象.有关对象处理的详细信息,请参阅最佳实践:使用一次性Windows SharePoint Services对象.
这种情况发生在每个MOSS程序员和某些方面.
ASP.NET:
如果您正在使用Linq-To-SQL,则调用SubmitChanges()
数据上下文并抛出异常(例如重复键或其他约束违规),在调试时,有问题的对象值会保留在您的内存中,并且每次都会重新提交你随后打电话SubmitChanges()
.
现在这里是真正的踢球者:即使你按下IDE中的"停止"按钮并重新启动,坏值也将保留在内存中!我不明白为什么有人认为这是个好主意 - 但是系统托盘中弹出的那个小的ASP.NET图标仍然在运行,它似乎可以保存你的对象缓存.如果要刷新内存空间,则必须右键单击该图标并强制关闭它!GOTCHA!
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)那篇精彩的书面和有用的帖子,但它仍然有点奇怪.你同意吗?
我经常要提醒自己,DateTime是一个值类型,而不是ref类型.对我来说似乎太奇怪了,特别是考虑到它的各种构造函数.
到目前为止,我最糟糕的一个,我今天才知道...如果你覆盖object.Equals(object obj),你可以发现:
((MyObject)obj).Equals(this);
表现不一样:
((MyObject)obj) == this;
一个会调用你的覆盖函数,另一个不会.
对于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提交一个?
必须按顺序添加Oracle参数
这是Oracle参数化查询的ODP .Net实现中的一个主要问题.
向查询添加参数时,默认行为是忽略参数名称,并按照添加顺序使用这些值.
解决方案是BindByName
将OracleCommand
对象的属性设置为true
- false
默认情况下......这是定性的(如果不是非常定量的)类似于DropDatabaseOnQueryExecution
使用默认值调用的属性true
.
他们称之为功能 ; 我把它称为公共领域的一个坑.
有关详细信息,请参见此处
看看这个:
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/