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

问题描述 投票:374回答:61

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

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

AddDays()的intellisense文档说它在日期中添加了一天,但它没有 - 它实际上返回了添加了一天的日期,所以你必须像下面这样写:

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

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

c# .net
61个回答
299
投票
private int myVar;
public int MyVar
{
    get { return MyVar; }
}

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

(注意资本MyVar而不是getter中的小写myVar。)


48
投票
string.Equals(a, b)

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


46
投票

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

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


DateTime.ToString(“r”)应该转换为使用GMT的RFC1123。格林尼治标准时间距离UTC只有几分之一秒,而“r”格式说明符List<string>,即使有问题的DateTime被指定为Local。

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

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

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

哎呦!


44
投票

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

does not convert to UTC

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


40
投票

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

DateTime resolution

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

这段代码:

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;

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

这个特殊的行为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 在这个日期/时间API。


Path.Combine

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

如果第二个参数以varies from system to system字符开头,它将不会为您提供完整的路径:

这段代码:

there are other resolution-related gotchas

给你这个输出:

\

38
投票

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


34
投票

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

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

简而言之,在Linq-To-Sql查询的条件子句中,您不能使用C:\MyFolder\MySubFolder\log\ \log\ C:\MyFolder\MySubFolder\log\ \log\ here等条件快捷方式来避免空引用异常; Linq-To-Sql评估OR或AND运算符的两侧,即使第一个条件不需要评估第二个条件!


29
投票

使用虚拟方法的默认参数

||

输出: 派生基础


27
投票

可变集合中的值对象

&&

没有效果。

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 { ... } List<Point> mypoints = ...; mypoints[i].x = 10; 值对象的副本。 C#happily允许您修改副本的字段。默默无闻。


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

mypoints[i]

25
投票

也许不是最糟糕的,但.net框架Point的某些部分,而其他人使用Cannot modify the return value of 'System.Collections.Generic.List<Foo>.this[int]' because it is not a variable (和Intellisense出现的文档从不告诉你哪个,你必须访问MSDN才能找到)

所有这一切都可以通过使用use degrees类来避免......


22
投票

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

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

radians

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

Angle

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

网上有各种与两者之间的差异有关的信息(例如, class A { public: int i; }; )。我强烈鼓励任何过渡到C#的人至少掌握差异及其含义的实际知识。


252
投票

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<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
        }
    }
}

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

我的brainteasers page上还有其他奇怪之处。


19
投票

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


18
投票

foreach循环变量范围!

    struct A
    {
        int i;
    };

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

here

18
投票

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


18
投票

数组实现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());

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

编译,但不起作用:

DateTime.MinDate

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


18
投票

令人讨厌的Linq Caching Gotcha

参见导致这一发现的IList,以及发现问题的IList<int> myList = new int[] { 1, 2, 4 }; myList.Add(5);

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

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

GOTCHA!


17
投票

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

the blogger

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

它看起来与// 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); 非常相似没有任何帮助,如果它返回没有异常,它保证写入所有字节。上述代码几乎一直在工作也没有帮助。当然,没有现成的,方便的方法正确读取N个字节也无济于事。

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

Stream.Read

14
投票

活动

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


14
投票

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

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

这打印5 10 5


13
投票

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

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

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

List<delegate>

想象一下,当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); } } 抛出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,这样你就会获得一组不同的临时文件名。

当然,解决方案是使用filesPath.GetTempFilename()来急切枚举值:

ToArray()

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

ToList()

你发现所有的写作后var files = Enumerable.Range(0, 5) .Select(i => Path.GetTempFileName()) .ToArray(); 仍然是0!然后你开始严格检查你没有竞争条件......在一个浪费时间之后......你发现它只是那个小小的可疑的东西你忘记了......


13
投票

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

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

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

193
投票

Re-throwing exceptions

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

很多时候我看到如下代码

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

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

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

catch(Exception)
{
    throw;
}

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

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

10
投票
content.Length

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


193
投票

海森堡观察窗

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

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显然是null,对吧?呃......所以...为什么跳过这个条件直接去return _myObj?!你将鼠标悬停在_myObj上......事实上,它确实有价值!那是怎么发生的?!

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

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

GOTCHA!


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

使用[DebuggerBrowsable(DebuggerBrowsableState.Never)]或[DebuggerDisplay(“<loaded on demand>”)]装饰属性。 - 克里斯蒂安·海特


144
投票

这是另一次让我:

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

qazxsw poi是时间跨度的第二部分(2分0秒的秒值为0)。

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


80
投票

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

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

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

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

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

TimeSpan.Seconds

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


63
投票

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

TimeSpan.TotalSeconds

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

怎么解决这个?我以为一个班轮就可以完成这项任务。我期待Image image = System.Drawing.Image.FromFile("nice.pic"); 的额外参数,但没有,所以我写了这个......

"nice.pic"

51
投票

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


50
投票

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

FromFile()

解决方案?

  • 在比较字符串类型时总是使用using (Stream fs = new FileStream("nice.pic", FileMode.Open, FileAccess.Read)) { image = System.Drawing.Image.FromStream(fs); }
  • 使用像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 这样的泛型来确保两个操作数都是字符串。
© www.soinside.com 2019 - 2024. All rights reserved.