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

C#或.NET中最糟糕的问题是什么?,c#,.net,C#,.net,我最近正在处理一个DateTime对象,并编写了如下内容: DateTime dt = DateTime.Now; dt.AddDays(1); return dt; // still today's date! WTF? using System; using System.Threading; class Test { static void Main() { for (int i=0; i < 10; i++) {

我最近正在处理一个
DateTime
对象,并编写了如下内容:

DateTime dt = DateTime.Now;
dt.AddDays(1);
return dt; // still today's date! WTF?
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();
        }
    }
}
private MyClass _myObj;
public MyClass MyObj {
  get {
    if (_myObj == null)
      _myObj = CreateMyObj(); // some other code to create my object
    return _myObj;
  }
}
AddDays()
的intellisense文档说它在日期上添加了一天,但实际上它并没有这样做-它实际上返回了一个添加了一天的日期,因此您必须像这样编写它:

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

这本书之前已经咬过我好几次了,所以我想把最糟糕的C#gotchas分类会很有用。

我前几天看到这本书发了出来,我认为这本书相当晦涩,对那些不知道的人来说是痛苦的

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

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

我见过很多人被咬的是。他们想知道为什么它适用于自己程序集中的类型,以及一些类型,如
System.String
,但不适用于
System.Windows.Forms.Form
。答案是它只在当前程序集中和
mscorlib
中查找


匿名方法

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();
C#2.0引入了匿名方法,导致了如下恶劣情况:

DateTime dt = DateTime.Now;
dt.AddDays(1);
return dt; // still today's date! WTF?
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();
        }
    }
}
private MyClass _myObj;
public MyClass MyObj {
  get {
    if (_myObj == null)
      _myObj = CreateMyObj(); // some other code to create my object
    return _myObj;
  }
}

迭代器块的延迟执行

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();
这个“可怜的人的单元测试”没有通过——为什么不呢

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
        }
    }
}
使用系统;
使用System.Collections.Generic;
使用系统诊断;
课堂测试
{
静态IEnumerable大写字母表(字符串输入)
{
如果(输入==null)
{
抛出新的ArgumentNullException(输入);
}
foreach(输入中的字符c)
{
产率-返回焦度(c);
}
}
静态void Main()
{
//测试空输入是否正确处理
尝试
{
CapitalLetters(空);
WriteLine(“应该抛出异常!”);
}
捕获(异常)
{
//期望
}
}
}
答案是,
CapitalLetters
代码源中的代码在迭代器的
MoveNext()
方法首次调用之前不会执行


我的上还有一些其他的奇怪之处。

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

解决方案

  • 比较字符串类型时,请始终使用
    string.Equals(a,b)

  • 使用诸如
    List
    之类的泛型来确保两个操作数都是字符串


这是另一个让我感动的时刻:

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

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

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

布拉姆莫。你的应用程序崩溃,没有堆栈跟踪。这种事经常发生


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

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

当您启动一个进程(使用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());
var l=新列表();
var strings=new[]{“Lorem”、“ipsum”、“dolor”、“sit”、“amet”};
foreach(字符串中的变量s)
{
l、 添加(()=>s);
}
foreach(l中的变量a)
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());
var l=新列表();
var strings=new[]{“Lorem”、“ipsum”、“dolor”、“sit”、“amet”};
foreach(字符串中的变量s)
{
var t=s;
l、 添加(()=>t);
}
foreach(l中的变量a)
Console.WriteLine(a());
可变集合中的值对象
如果算上ASP.NET,我会说webforms生命周期对我来说是一个相当大的难题。我花了无数个小时调试写得很糟糕的webforms代码,只是因为很多开发人员不知道何时使用哪个事件处理程序(遗憾的是,我也包括在内)。

重新抛出异常 重新抛出异常语义(re-throw exception semantics)是一个吸引了大量新开发人员的难题

很多时候我会看到如下代码

catch(Exception e) 
{
   // Do stuff 
   throw e; 
}
问题是它会抹去堆栈跟踪,使诊断问题变得更加困难,因为您无法跟踪异常的起源

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

catch(Exception)
{
    throw;
}
或者将异常包装到另一个异常中,并使用内部异常获取原始堆栈跟踪:

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

对于C/C++程序员来说,向C#的过渡是很自然的。然而,我个人遇到的最大的难题(也看到其他人在进行同样的转换)是没有完全理解C#中类和结构之间的区别

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

    class A
    {
    public:
        int i;
    };
在功能上等同于此结构定义

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

网络上有各种各样的信息与两者之间的差异有关(例如,)。我强烈鼓励任何向C#过渡的人至少对差异及其含义有一个工作知识。

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

正确的方法是使用DateTime.ToString(“dd'/'MM'/'yyyy”)
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);
    }
}
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
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 ;)
timer.Tick -= TimerTickEventHandler;
Image image = System.Drawing.Image.FromFile("nice.pic");
using (Stream fs = new FileStream("nice.pic", FileMode.Open, FileAccess.Read))
{
    image = System.Drawing.Image.FromStream(fs);
}
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);
var files = Enumerable.Range(0, 5)
    .Select(i => Path.GetTempFileName())
    .ToArray();
foreach (var file in files)
    content = content + File.ReadAllText(file);
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
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!"
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<int> myList = new int[] { 1, 2, 4 };
myList.Add(5);
// 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);
    /// <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;
    }
int? i = null;
i++; // I would have expected an exception but runs fine and stays as null