Gotcha tồi tệ nhất trong C # hoặc .NET là gì? [đóng cửa]


377

Gần đây tôi đã làm việc với một DateTimeđối tượng và đã viết một cái gì đó như thế này:

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

Tài liệu intellisense cho AddDays()biết nó thêm một ngày vào ngày mà nó không - nó thực sự trả về một ngày với một ngày được thêm vào nó, vì vậy bạn phải viết nó như sau:

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

Điều này đã cắn tôi một số lần trước đây, vì vậy tôi nghĩ rằng nó sẽ hữu ích để lập danh mục các C # gotchas tồi tệ nhất.


157
trả về DateTime.Now.AddDays (1);
crashmstr

23
AFAIK, các loại giá trị tích hợp đều không thay đổi, ít nhất là trong đó bất kỳ phương thức nào có trong loại đó đều trả về một mục mới thay vì sửa đổi mục hiện có. Ít nhất, tôi không thể nghĩ ra một cái trên đỉnh đầu mà không làm điều này: tất cả đều tốt đẹp và nhất quán.
Joel Coehoorn

6
Loại giá trị có thể thay đổi: System.Collections.Generics.List.Enumerator :( (Và vâng, bạn có thể thấy nó hoạt động kỳ quặc nếu bạn cố gắng hết sức.)
Jon Skeet

13
Intellisense cung cấp cho bạn tất cả các thông tin bạn cần. Nó nói rằng nó trả về một đối tượng DateTime. Nếu nó chỉ thay đổi cái bạn truyền vào, nó sẽ là một phương thức void.
John Kraft

20
Không nhất thiết: StringBuilder.Append (...) trả về "cái này" chẳng hạn. Điều đó khá phổ biến trong các giao diện trôi chảy.
Jon Skeet

Câu trả lời:


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

Blammo. Ứng dụng của bạn gặp sự cố không có dấu vết ngăn xếp. Xảy ra mọi lúc.

(Thông báo vốn MyVarthay vì chữ thường myVartrong getter.)


112
và SO thích hợp cho trang web này :)
gbjbaanb

62
Tôi đặt dấu gạch dưới vào thành viên tư nhân, giúp đỡ rất nhiều!
chakrit

61
Tôi sử dụng các thuộc tính tự động khi tôi có thể, dừng loại vấn đề này rất nhiều;)
TWith2Sugars

28
Đây là một lý do TUYỆT VỜI để sử dụng tiền tố cho các lĩnh vực riêng tư của bạn (có những tiền tố khác, nhưng đây là một lý do tốt): _myVar, m_myVar
jrista

205
@jrista: O làm ơn KHÔNG ... không phải m_ ... aargh kinh dị ...
fretje

254

Loại.GetType

Người mà tôi đã thấy cắn nhiều người là Type.GetType(string). Họ tự hỏi tại sao nó hoạt động cho các loại trong lắp ráp riêng của họ, và một số loại như System.String, nhưng không System.Windows.Forms.Form. Câu trả lời là nó chỉ nhìn vào lắp ráp hiện tại và trong mscorlib.


Phương pháp ẩn danh

C # 2.0 đã giới thiệu các phương thức ẩn danh, dẫn đến các tình huống khó chịu như thế này:

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

Điều đó sẽ in ra cái gì? Vâng, nó hoàn toàn phụ thuộc vào lịch trình. Nó sẽ in 10 số, nhưng có lẽ nó sẽ không in 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, đó là những gì bạn có thể mong đợi. Vấn đề là đó là ibiến đã được nắm bắt, không phải là giá trị của nó tại điểm tạo ra đại biểu. Điều này có thể được giải quyết dễ dàng với một biến cục bộ bổ sung có phạm vi đúng:

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

Trì hoãn thực hiện các khối lặp

"Bài kiểm tra đơn vị của người nghèo" này không vượt qua - tại sao không?

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

Câu trả lời là mã trong nguồn của CapitalLettersmã không được thực thi cho đến khi MoveNext()phương thức của trình vòng lặp được gọi lần đầu tiên.

Tôi đã có một số điều kỳ lạ khác trên trang brainteasers của tôi .


25
Ví dụ lặp là sai lệch!
Jimmy

8
Tại sao không chia câu này thành 3 câu trả lời để chúng ta có thể bình chọn từng câu thay vì cùng nhau?
chakrit

13
@chakrit: Nhìn lại, đó có lẽ là một ý tưởng tốt, nhưng tôi nghĩ bây giờ đã quá muộn. Có vẻ như tôi chỉ đang cố gắng để có thêm đại diện ...
Jon Skeet

19
Trên thực tế Type.GetType hoạt động nếu bạn cung cấp AssociationQualifiedName. Type.GetType ("System.ServiceModel.EndpointNotFoundException, System.ServiceModel, Version = 3.0.0.0, Culture = trung tính, PublicKeyToken = b77a5c561934e089");
chilltemp

2
@kentaromiura: Độ phân giải quá tải bắt đầu từ loại có nguồn gốc nhất và hoạt động trên cây - nhưng chỉ nhìn vào các phương thức được khai báo ban đầu theo loại mà nó đang tìm kiếm. Foo (int) ghi đè phương thức cơ bản, vì vậy không được xem xét. Foo (đối tượng) được áp dụng, vì vậy độ phân giải quá tải dừng ở đó. Lạ, tôi biết.
Jon Skeet

194

Ném lại ngoại lệ

Một gotcha nhận được rất nhiều nhà phát triển mới, là ngữ nghĩa ngoại lệ.

Nhiều thời gian tôi thấy mã như sau

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

Vấn đề là nó xóa sạch dấu vết ngăn xếp và làm cho việc chẩn đoán các vấn đề khó khăn hơn nhiều, vì bạn không thể theo dõi ngoại lệ bắt nguồn từ đâu.

Mã chính xác là câu lệnh throw không có đối số:

catch(Exception)
{
    throw;
}

Hoặc gói ngoại lệ trong một ngoại lệ khác và sử dụng ngoại lệ bên trong để có được dấu vết ngăn xếp ban đầu:

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

Rất may mắn, tôi đã được ai đó dạy về điều này trong tuần đầu tiên của mình và tìm thấy nó trong mã của các nhà phát triển cao cấp hơn. Là: bắt () {ném; } Giống như đoạn mã thứ hai? bắt (Ngoại lệ e) {ném; } chỉ có nó không tạo ra một đối tượng Ngoại lệ và điền vào nó?
StuperUser

Bên cạnh lỗi sử dụng throw ex (hoặc throw e) thay vì chỉ ném, tôi phải tự hỏi những trường hợp nào xảy ra khi đáng để bắt một ngoại lệ chỉ để ném lại.
Ryan Lundy

13
@Kyralessa: có nhiều trường hợp: ví dụ: nếu bạn muốn quay lại giao dịch, trước khi người gọi nhận được ngoại lệ. Bạn quay lại và sau đó suy nghĩ lại.
R. Martinho Fernandes

7
Tôi thấy điều này mọi lúc mọi người bắt và rút lại các ngoại lệ chỉ vì họ được dạy rằng họ phải nắm bắt tất cả các ngoại lệ, không nhận ra rằng nó sẽ bị bắt thêm trong ngăn xếp cuộc gọi. Nó lái tôi hạt dẻ.
James Westgate

5
@Kyralessa trường hợp lớn nhất là khi bạn phải đăng nhập. Ghi lại lỗi bắt và rút lại ..
nawfal

194

Cửa sổ đồng hồ Heisenberg

Điều này có thể cắn bạn rất tệ nếu bạn đang làm những thứ theo yêu cầu, như thế này:

private MyClass _myObj;
public MyClass MyObj {
  get {
    if (_myObj == null)
      _myObj = CreateMyObj(); // some other code to create my object
    return _myObj;
  }
}

Bây giờ hãy nói rằng bạn có một số mã ở nơi khác bằng cách sử dụng này:

// blah
// blah
MyObj.DoStuff(); // Line 3
// blah

Bây giờ bạn muốn gỡ lỗi CreateMyObj()phương thức của bạn . Vì vậy, bạn đặt một điểm dừng trên Dòng 3 ở trên, với ý định bước vào mã. Chỉ cần cho biện pháp tốt, bạn cũng đặt một điểm dừng trên dòng trên đó _myObj = CreateMyObj();, và thậm chí là một điểm dừng bên trong CreateMyObj()chính nó.

Mã đạt điểm dừng của bạn trên Dòng 3. Bạn bước vào mã. Bạn muốn nhập mã có điều kiện, vì _myObjrõ ràng là null, phải không? Uh ... vậy ... tại sao nó lại bỏ qua điều kiện và đi thẳng tới return _myObj?! Bạn di chuột qua _myObj ... và thực sự, nó có giá trị! Làm thế nào điều đó xảy ra?!

Câu trả lời là IDE của bạn khiến nó nhận được giá trị, bởi vì bạn mở cửa sổ "xem" - đặc biệt là cửa sổ đồng hồ "Ô tô", hiển thị các giá trị của tất cả các biến / thuộc tính có liên quan đến dòng thực thi hiện tại hoặc trước đó. Khi bạn đạt điểm dừng của mình trên Dòng 3, cửa sổ đồng hồ quyết định rằng bạn sẽ quan tâm để biết giá trị của MyObj- vì vậy, đằng sau hậu trường, bỏ qua bất kỳ điểm dừng nào của bạn , nó đã đi và tính giá trị của MyObjbạn - bao gồm cả cuộc gọi đến CreateMyObj()đó đặt giá trị của _myObj!

Đó là lý do tại sao tôi gọi đây là Cửa sổ Đồng hồ Heisenberg - bạn không thể quan sát giá trị mà không ảnh hưởng đến nó ... :)

GOTCHA!


Chỉnh sửa - Tôi cảm thấy bình luận của @ ChristianHayter xứng đáng được đưa vào câu trả lời chính, bởi vì nó có vẻ như là một cách giải quyết hiệu quả cho vấn đề này. Vì vậy, bất cứ lúc nào bạn có một tài sản tải lười biếng ...

Trang trí tài sản của bạn bằng [DebuggerBrowable (DebuggerBrowableState.Never)] hoặc [DebuggerDisplay ("<được tải theo yêu cầu>")]. - Christian Hayter


10
rực rỡ tìm thấy! bạn không phải là lập trình viên, bạn là một người sửa lỗi thực sự.
cái này __cpered_geek

26
Tôi đã chạy vào đây thậm chí di chuột qua biến, không chỉ cửa sổ xem.
Richard Morgan

31
Trang trí tài sản của bạn với [DebuggerBrowsable(DebuggerBrowsableState.Never)]hoặc [DebuggerDisplay("<loaded on demand>")].
Christian Hayter

4
Nếu bạn đang phát triển lớp khung và muốn chức năng cửa sổ xem mà không thay đổi hành vi thời gian chạy của thuộc tính được xây dựng lười biếng, bạn có thể sử dụng proxy loại trình gỡ lỗi để trả về giá trị nếu nó đã được xây dựng và thông báo rằng thuộc tính không có được xây dựng nếu đó là trường hợp. Các Lazy<T>lớp (đặc biệt cho sản phẩm Valuebất động sản) là một ví dụ về nơi này được sử dụng.
Sam Harwell

4
Tôi nhớ lại một người (vì một số lý do tôi không thể hiểu được) đã thay đổi giá trị của đối tượng trong tình trạng quá tải ToString. Mỗi lần anh ta di chuột qua nó, tooltip mang lại cho anh ta một giá trị khác - anh ta không thể hiểu được ...
JNF

144

Đây là một lần khác có được tôi:

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

TimeSpan.Seconds là phần giây của khoảng thời gian (2 phút và 0 giây có giá trị giây là 0).

TimeSpan.TotalSeconds là toàn bộ thời gian được tính bằng giây (2 phút có tổng giá trị giây là 120).


1
Vâng, người đó cũng đã có tôi. Tôi nghĩ rằng nó nên là TimeSpan.SecondsPart hoặc một cái gì đó để làm cho nó rõ ràng hơn những gì nó đại diện.
Dan Diplo

3
Khi đọc lại điều này, tôi phải tự hỏi tại sao TimeSpanthậm chí một Secondstài sản. Dù sao thì ai cũng cho con chuột biết phần giây của thời gian là gì? Đó là một giá trị tùy ý, phụ thuộc vào đơn vị; Tôi không thể hình dung được bất kỳ việc sử dụng thực tế nào cho nó.
MusiGenesis

2
Điều này có ý nghĩa với tôi rằng TimeSpan.TotalSeconds sẽ trả về ... tổng số giây trong khoảng thời gian.
Ed S.

16
@MusiGenesis tài sản là hữu ích. Điều gì sẽ xảy ra nếu tôi muốn hiển thị thời gian vỡ ra thành từng mảnh? Ví dụ: giả sử Timespan của bạn thể hiện thời lượng '3 giờ 15 phút 10 giây'. Làm thế nào bạn có thể truy cập thông tin này mà không có thuộc tính Giây, Giờ, Phút?
SolutionYogi

1
Trong các API tương tự, tôi đã sử dụng SecondsPartSecondsTotalđể phân biệt hai loại này.
BlueRaja - Daniel Pflughoeft

80

Rò rỉ bộ nhớ vì bạn đã không bỏ các sự kiện.

Điều này thậm chí đã bắt được một số nhà phát triển cao cấp mà tôi biết.

Hãy tưởng tượng một hình thức WPF có rất nhiều thứ trong đó, và ở đâu đó trong đó bạn đăng ký một sự kiện. Nếu bạn không hủy đăng ký thì toàn bộ biểu mẫu sẽ được giữ xung quanh trong bộ nhớ sau khi được đóng và hủy tham chiếu.

Tôi tin rằng vấn đề mà tôi đã thấy là tạo một DispatchTimer trong biểu mẫu WPF và đăng ký sự kiện Tick, nếu bạn không thực hiện - = trên bộ đếm thời gian biểu mẫu của bạn bị rò rỉ bộ nhớ!

Trong ví dụ này, mã phân tích của bạn nên có

timer.Tick -= TimerTickEventHandler;

Điều này đặc biệt khó khăn vì bạn đã tạo phiên bản của DispatchTimer bên trong biểu mẫu WPF, vì vậy bạn sẽ nghĩ rằng đó sẽ là một tham chiếu nội bộ được xử lý bởi quy trình Thu gom rác ... thật không may, DispatchTimer sử dụng danh sách đăng ký và dịch vụ nội bộ tĩnh các yêu cầu trên luồng UI, vì vậy tham chiếu được 'sở hữu' bởi lớp tĩnh.


1
Bí quyết là luôn luôn phát hành tất cả các đăng ký sự kiện bạn tạo. Nếu bạn bắt đầu dựa vào Biểu mẫu thực hiện nó cho bạn, bạn có thể chắc chắn rằng mình sẽ có thói quen và một ngày nào đó sẽ quên phát hành một sự kiện ở đâu đó nơi cần thực hiện.
Jason Williams

3
Có một đề xuất kết nối MS cho các sự kiện tham chiếu yếu ở đây sẽ giải quyết vấn đề này, mặc dù theo tôi, chúng ta chỉ nên thay thế hoàn toàn mô hình sự kiện cực kỳ nghèo nàn bằng mô hình kết hợp yếu, như CAB đã sử dụng.
BlueRaja - Daniel Pflughoeft

+1 từ tôi, cảm ơn! Vâng, không, cảm ơn cho công việc xem xét mã tôi đã phải làm!
Bob Denny

@ BlueRaja-DannyPflughoeft Với ​​các sự kiện yếu, bạn có một hình ảnh xác thực khác - bạn không thể đăng ký lambdas. Bạn không thể viếttimer.Tick += (s, e,) => { Console.WriteLine(s); }
Ark-kun

@ Ark-kun có lambdas làm cho nó thậm chí còn khó hơn, bạn sẽ phải lưu lambda của bạn vào một biến và sử dụng nó trong mã xé của bạn. Kinda phá hủy sự đơn giản của việc viết lambdas phải không?
Timothy Walters

63

Có thể không thực sự là một gotcha vì hành vi được viết rõ ràng trong MSDN, nhưng đã bị gãy cổ tôi một lần vì tôi thấy nó khá phản trực giác:

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

Anh chàng này để các "nice.pic"tập tin bị khóa cho đến khi hình ảnh được xử lý. Vào thời điểm tôi đối mặt với nó, mặc dù thật tuyệt khi tải các biểu tượng một cách nhanh chóng và không nhận ra (lúc đầu) tôi đã kết thúc với hàng tá tệp mở và bị khóa! Hình ảnh theo dõi nơi nó đã tải tệp từ ...

Làm thế nào để giải quyết điều này? Tôi nghĩ rằng một lót sẽ làm công việc. Tôi mong đợi một tham số bổ sung cho FromFile(), nhưng không có tham số nào, vì vậy tôi đã viết ...

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

10
Tôi đồng ý rằng hành vi này không có ý nghĩa. Tôi không thể tìm thấy bất kỳ lời giải thích nào cho nó ngoài "hành vi này là do thiết kế".
MusiGenesis

1
Ồ và điều tuyệt vời về cách giải quyết này là nếu bạn cố gắng gọi Image.ToStream (tôi quên tên chính xác ngoài tay) sau đó nó sẽ không hoạt động.
Joshua

55
cần kiểm tra một số mã. Brb.
Esben Skov Pedersen

7
@EsbenSkovPedersen Một bình luận đơn giản nhưng hài hước và khô khan. Làm cho ngày của tôi.
Inisheer

51

Nếu bạn đếm ASP.NET, tôi sẽ nói rằng vòng đời của webforms là một vấn đề khá lớn đối với tôi. Tôi đã dành vô số giờ để gỡ lỗi mã webforms được viết kém, chỉ vì nhiều nhà phát triển thực sự không hiểu khi nào nên sử dụng trình xử lý sự kiện nào (bao gồm cả tôi, đáng buồn thay).


26
Đó là lý do tại sao tôi chuyển sang MVC ... đau đầu khi xem ...
chakrit

29
Có một câu hỏi hoàn toàn khác dành riêng cho ASP.NET gotchas (xứng đáng như vậy). Khái niệm cơ bản về ASP.NET (làm cho các ứng dụng web có vẻ giống như các ứng dụng Windows cho nhà phát triển) bị nhầm lẫn khủng khiếp đến nỗi tôi không chắc nó thậm chí còn được tính là "gotcha".
MusiGenesis

1
MusiGenesis Tôi ước tôi có thể bình chọn bình luận của bạn một trăm lần.
csauve

3
@MusiGenesis Hiện tại có vẻ sai lầm, nhưng vào thời điểm đó, mọi người muốn các ứng dụng web của họ (ứng dụng là từ khóa - ASP.NET WebForms không thực sự được thiết kế để lưu trữ blog) để hoạt động giống như các ứng dụng windows của họ. Điều này chỉ thay đổi tương đối gần đây và nhiều người vẫn "không ở đó". Toàn bộ vấn đề là trừu tượng là cách quá rò rỉ - web không hoạt động giống như một ứng dụng máy tính để bàn rất nhiều nó dẫn đến sự nhầm lẫn ở hầu hết tất cả mọi người.
Luaan

1
Trớ trêu thay, điều đầu tiên tôi từng thấy về ASP.NET là một video từ Microsoft cho thấy bạn có thể dễ dàng tạo một trang blog bằng ASP.NET như thế nào!
MusiGenesis

51

quá tải == toán tử và các container chưa được xử lý (danh sách mảng, bộ dữ liệu, v.v.):

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

Các giải pháp?

  • luôn luôn sử dụng string.Equals(a, b)khi bạn so sánh các loại chuỗi

  • sử dụng khái quát như List<string>để đảm bảo rằng cả hai toán hạng là chuỗi.


6
Bạn đã có thêm khoảng trắng trong đó khiến tất cả đều sai - nhưng nếu bạn lấy khoảng trắng ra, dòng cuối cùng vẫn sẽ đúng vì "chuỗi" + "của tôi" vẫn là một hằng số.
Jon Skeet

1
ôi! bạn nói đúng :) ok, tôi đã chỉnh sửa một chút.
Jimmy

một cảnh báo được tạo ra trên việc sử dụng như vậy.
chakrit

11
Đúng, một trong những lỗ hổng lớn nhất với ngôn ngữ C # là toán tử == trong lớp Object. Họ nên đã buộc chúng tôi sử dụng ReferenceEquals.
erikkallen

2
Rất may, kể từ 2.0 chúng tôi đã có thuốc generic. Sẽ ít phải lo lắng hơn nếu bạn đang sử dụng Danh sách <chuỗi> trong ví dụ trên thay vì ArrayList. Thêm vào đó, chúng tôi đã đạt được hiệu suất từ ​​nó, yay! Tôi luôn luôn root các tham chiếu cũ đến ArrayLists trong mã kế thừa của chúng tôi.
JoelC

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

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

Đạo đức của câu chuyện: Trình khởi tạo trường không được chạy khi khử lưu trữ đối tượng


8
Có, tôi ghét tuần tự hóa .NET vì không chạy hàm tạo mặc định. Tôi ước rằng không thể xây dựng một đối tượng mà không gọi bất kỳ nhà xây dựng nào, nhưng than ôi không phải vậy.
Roman Starkov

45

DateTime.ToString ("dd / MM / yyyy") ; Điều này thực sự sẽ không phải lúc nào cũng cung cấp cho bạn dd / MM / yyyy mà thay vào đó, nó sẽ tính đến các cài đặt khu vực và thay thế dấu phân cách ngày của bạn tùy thuộc vào vị trí của bạn. Vì vậy, bạn có thể nhận được dd-MM-yyyy hoặc một cái gì đó tương tự.

Cách đúng đắn để làm điều này là sử dụng DateTime.ToString ("dd '/' MM '/' yyyy");


DateTime.ToString ("r") được cho là chuyển đổi thành RFC1123, sử dụng GMT. GMT nằm trong một phần của giây từ UTC và tuy nhiên, chỉ định định dạng "r" không chuyển đổi sang UTC , ngay cả khi DateTime trong câu hỏi được chỉ định là Cục bộ.

Điều này dẫn đến gotcha sau (thay đổi tùy theo thời gian địa phương của bạn đến từ UTC):

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

Rất tiếc!


19
Thay đổi mm thành MM - mm là phút và MM là tháng. Một Gotcha khác, tôi đoán ...
Kobi

1
Tôi có thể thấy đây sẽ là một hình ảnh xác thực như thế nào nếu bạn không biết điều đó (tôi đã không) ... nhưng tôi đang cố gắng tìm ra khi nào bạn muốn có hành vi mà bạn đang cố gắng in một ngày cụ thể không phù hợp với những gì thiết lập khu vực của bạn.
Beska

6
@Beska: Bởi vì bạn đang ghi vào một tệp, cần phải ở một định dạng cụ thể, với định dạng ngày được chỉ định.
GvS

11
Tôi cho rằng các mặc định được bản địa hóa là tồi tệ hơn so với cách khác. Ít nhất là nhà phát triển đã bỏ qua nội địa hóa hoàn toàn mã hoạt động trên các máy được bản địa hóa khác nhau. Bằng cách này, mã có thể không hoạt động.
Joshua

32
Trên thực tế tôi tin rằng cách chính xác để làm điều này sẽ làDateTime.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture);
BlueRaja - Danny Pflughoeft

44

Tôi đã thấy cái này được đăng vào một ngày khác và tôi nghĩ nó khá mơ hồ và đau đớn cho những người không biết

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

Vì điều đó sẽ trả về 0 chứ không phải 1 như hầu hết mong đợi


37
Tôi hy vọng rằng sẽ không thực sự cắn người mặc dù - tôi thực sự hy vọng họ sẽ không viết nó ngay từ đầu! (Tất nhiên là thú vị, tất nhiên.)
Jon Skeet

12
Tôi không nghĩ điều này rất mơ hồ ...
Chris Marasti-Georg

10
Ít nhất, trong C #, kết quả được xác định, nếu bất ngờ. Trong C ++, nó có thể là 0 hoặc 1 hoặc bất kỳ kết quả nào khác bao gồm cả chấm dứt chương trình!
James Curran

7
Đây không phải là một gotcha; x = x ++ -> x = x, sau đó tăng x .... x = ++ x -> tăng x rồi x = x
Kevin

28
@Kevin: Tôi không nghĩ nó khá đơn giản. Nếu x = x ++ tương đương với x = x theo sau là x ++, thì kết quả sẽ là x = 1. Thay vào đó, tôi nghĩ điều gì xảy ra trước tiên là biểu thức ở bên phải dấu bằng được đánh giá (cho 0), sau đó x là tăng (cho x = 1) và cuối cùng việc gán được thực hiện (cho x = 0 một lần nữa).
Tim Goodman

39

Tôi đến bữa tiệc này hơi muộn, nhưng tôi có hai chú chó vừa cắn tôi gần đây:

Độ phân giải DateTime

Thuộc tính Ticks đo thời gian trong 10 phần triệu giây (khối 100 nano giây), tuy nhiên độ phân giải không phải là 100 nano giây, khoảng 15ms.

Mã này:

long now = DateTime.Now.Ticks;
for (int i = 0; i < 10; i++)
{
    System.Threading.Thread.Sleep(1);
    Console.WriteLine(DateTime.Now.Ticks - now);
}

sẽ cung cấp cho bạn một đầu ra của (ví dụ):

0
0
0
0
0
0
0
156254
156254
156254

Tương tự, nếu bạn xem DateTime.Now.Millisecond, bạn sẽ nhận được các giá trị trong các khối tròn 15.625ms: 15, 31, 46, v.v.

Hành vi đặc biệt này thay đổi tùy theo hệ thống , nhưng có các vấn đề khác liên quan đến độ phân giải trong API ngày / giờ này.


Path.Combine

Một cách tuyệt vời để kết hợp các đường dẫn tệp, nhưng nó không phải lúc nào cũng hoạt động theo cách bạn mong đợi.

Nếu tham số thứ hai bắt đầu bằng một \ký tự, nó sẽ không cung cấp cho bạn một đường dẫn hoàn chỉnh:

Mã này:

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

Cung cấp cho bạn đầu ra này:

C:\MyFolder\MySubFolder\log\
\log\
C:\MyFolder\MySubFolder\log\
\log\

17
Việc lượng tử hóa thời gian trong các khoảng thời gian ~ 15ms không phải do thiếu độ chính xác trong cơ chế định thời cơ bản (tôi đã bỏ qua việc giải thích vấn đề này sớm hơn). Đó là vì ứng dụng của bạn đang chạy bên trong một hệ điều hành đa tác vụ. Windows sẽ kiểm tra ứng dụng của bạn sau mỗi 15ms hoặc lâu hơn, và trong khoảng thời gian ít ỏi, ứng dụng của bạn xử lý tất cả các tin nhắn được xếp hàng kể từ lát cuối cùng của bạn. Tất cả các cuộc gọi của bạn trong lát đó trả về cùng một lúc vì tất cả các cuộc gọi đều được thực hiện cùng một lúc một cách hiệu quả.
MusiGenesis

2
@MusiGenesis: Tôi biết (bây giờ) nó hoạt động như thế nào, nhưng dường như tôi hiểu sai khi có một biện pháp chính xác như vậy không thực sự chính xác. Nó giống như nói rằng tôi biết chiều cao của tôi tính bằng nanomet khi thực sự tôi chỉ làm tròn nó đến mười triệu gần nhất.
Damovisa

7
DateTime hoàn toàn có khả năng lưu trữ tối đa một tích tắc; đó là DateTime. Bây giờ không sử dụng độ chính xác đó.
Ruben

16
Thêm '\' là một gotcha cho nhiều người unix / mac / linux. Trong Windows, nếu có '\' hàng đầu, điều đó có nghĩa là chúng tôi muốn truy cập root của ổ đĩa (tức là C :) hãy thử nó trong một CDlệnh để xem ý tôi là gì .... 1) Goto C:\Windows\System322) Loại CD \Users3) Woah! Bây giờ bạn đang ở C:\Users... GOT IT? ... Path.Combine (@ "C: \ Windows \ System32", @ "\ Users") sẽ trả về \Userscó nghĩa là chính xác[current_drive_here]:\Users
chakrit

8
Ngay cả khi không có 'giấc ngủ', điều này cũng thực hiện theo cách tương tự. Điều này không có gì để làm với ứng dụng được lên lịch cứ sau 15 ms. Hàm gốc được gọi bởi DateTime.UtcNow, GetSystemTimeAsFileTime, dường như có độ phân giải kém.
Jimbo

38

Khi bạn bắt đầu một quá trình (sử dụng System.Diagnostics) ghi vào bảng điều khiển, nhưng bạn không bao giờ đọc luồng Console.Out, sau một lượng đầu ra nhất định, ứng dụng của bạn sẽ xuất hiện.


3
Điều tương tự vẫn có thể xảy ra khi bạn chuyển hướng cả thiết bị xuất chuẩn và thiết bị xuất chuẩn và sử dụng hai cuộc gọi ReadToEnd theo thứ tự. Để xử lý an toàn cả thiết bị xuất chuẩn và thiết bị xuất chuẩn, bạn phải tạo một luồng đọc cho mỗi trong số chúng.
Sebastiaan M

34

Không có phím tắt toán tử trong Linq-To-Sql

Xem ở đây .

Nói tóm lại, bên trong mệnh đề điều kiện của truy vấn Linq-To-Sql, bạn không thể sử dụng các phím tắt có điều kiện như ||&&để tránh các ngoại lệ tham chiếu null; Linq-To-Sql đánh giá cả hai mặt của toán tử OR hoặc AND ngay cả khi điều kiện thứ nhất không cần phải đánh giá điều kiện thứ hai!


8
GẠCH BRB, tối ưu hóa lại vài trăm truy vấn LINQ ...
tsilb

30

Sử dụng tham số mặc định với phương thức ảo

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

Đầu ra:
cơ sở dẫn xuất


10
Thật kỳ lạ, tôi nghĩ điều này là hoàn toàn rõ ràng. Nếu kiểu khai báo là Base, trình biên dịch sẽ lấy giá trị mặc định từ đâu nếu không Base? Tôi đã nghĩ đó là một chút nhiều hơn Gotcha mà giá trị mặc định có thể khác nhau nếu kiểu được khai báo là có nguồn gốc loại, mặc dù phương pháp gọi là (tĩnh) là phương pháp cơ bản.
Timwi

1
Tại sao một triển khai của một phương thức sẽ nhận được giá trị mặc định của một triển khai khác?
staafl

1
@staafl Đối số mặc định được giải quyết tại thời gian biên dịch, không phải thời gian chạy.
dòng chảy

1
Tôi muốn nói rằng gotcha này là các tham số mặc định nói chung - mọi người thường không nhận ra rằng chúng được giải quyết vào thời gian biên dịch, thay vì thời gian chạy.
Luaan

4
@FredOverflow, câu hỏi của tôi là khái niệm. Mặc dù hành vi có ý nghĩa trong việc thực hiện, nhưng nó không trực quan và có khả năng là một lỗi. IMHO trình biên dịch C # không nên cho phép thay đổi giá trị tham số mặc định khi ghi đè.
staafl

27

Các đối tượng giá trị trong các bộ sưu tập có thể thay đổi

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

mypoints[i].x = 10;

không có hiệu lực.

mypoints[i]trả về một bản sao của một Pointđối tượng giá trị. C # vui vẻ cho phép bạn sửa đổi một trường của bản sao. Âm thầm không làm gì cả.


Cập nhật: Điều này dường như được sửa trong C # 3.0:

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

6
Tôi có thể thấy lý do tại sao điều đó gây nhầm lẫn, xem xét rằng nó thực sự hoạt động với các mảng (trái với câu trả lời của bạn), nhưng không phải với các bộ sưu tập động khác, như Danh sách <Điểm>.
Lasse V. Karlsen

2
Bạn đúng. Cảm ơn. Tôi đã sửa câu trả lời của mình :). arr[i].attr=là cú pháp đặc biệt đối với mảng mà bạn có thể không mã trong container thư viện; (Tại sao (<biểu hiện giá trị>) attr = <expr> cho phép ở tất cả nó có thể bao giờ có ý nghĩa..?
Bjarke Ebert

1
@Bjarke Ebert: Có một số trường hợp sẽ có ý nghĩa, nhưng thật không may, không có cách nào để trình biên dịch xác định và cho phép những người đó. Kịch bản sử dụng mẫu: Cấu trúc bất biến chứa tham chiếu đến mảng hai chiều vuông cùng với chỉ báo "xoay / lật". Bản thân cấu trúc sẽ là bất biến, do đó, viết vào một phần tử của một thể hiện chỉ đọc sẽ ổn, nhưng trình biên dịch sẽ không biết rằng trình thiết lập thuộc tính sẽ không thực sự viết cấu trúc, và do đó sẽ không cho phép nó .
supercat

25

Có lẽ không phải là tồi tệ nhất, nhưng một số phần của khung .net sử dụng độ trong khi những phần khác sử dụng radian (và tài liệu xuất hiện với Intellisense không bao giờ cho bạn biết, bạn phải truy cập MSDN để tìm hiểu)

Tất cả điều này có thể tránh được bằng cách có một Anglelớp thay vào đó ...


Tôi ngạc nhiên này có rất nhiều upvotes, xem xét gotchas khác của tôi là tồi tệ hơn đáng kể này
BlueRaja - Danny Pflughoeft

22

Đối với các lập trình viên C / C ++, việc chuyển đổi sang C # là một điều tự nhiên. Tuy nhiên, vấn đề lớn nhất mà tôi gặp phải cá nhân (và đã thấy với những người khác thực hiện cùng một quá trình chuyển đổi) là không hiểu đầy đủ về sự khác biệt giữa các lớp và cấu trúc trong C #.

Trong C ++, các lớp và cấu trúc giống hệt nhau; chúng chỉ khác nhau về khả năng hiển thị mặc định, trong đó các lớp mặc định là khả năng hiển thị riêng tư và cấu trúc mặc định thành khả năng hiển thị công khai. Trong C ++, định nghĩa lớp này

    class A
    {
    public:
        int i;
    };

là chức năng tương đương với định nghĩa cấu trúc này.

    struct A
    {
        int i;
    };

Tuy nhiên, trong C #, các lớp là các kiểu tham chiếu trong khi các cấu trúc là các loại giá trị. Điều này làm cho một LỚN khác biệt trong (1) quyết định khi nào nên sử dụng cái này so với cái kia, (2) kiểm tra sự bình đẳng của đối tượng, (3) hiệu suất (ví dụ: đấm bốc / bỏ hộp), v.v.

Có tất cả các loại thông tin trên web liên quan đến sự khác biệt giữa hai (ví dụ, ở đây ). Tôi rất khuyến khích bất cứ ai thực hiện quá trình chuyển đổi sang C # để ít nhất có kiến ​​thức làm việc về sự khác biệt và ý nghĩa của chúng.


13
Vì vậy, vấn đề tồi tệ nhất là mọi người không bận tâm dành thời gian để học ngôn ngữ trước khi họ sử dụng nó?
BlueRaja - Daniel Pflughoeft

3
@ BlueRaja-DannyPflughoeft Giống như gotcha cổ điển của các ngôn ngữ rõ ràng tương tự nhau - chúng sử dụng các từ khóa rất giống nhau và trong nhiều trường hợp cú pháp, nhưng hoạt động theo nhiều cách khác nhau.
Luaan

19

Thu gom rác thải và vứt bỏ (). Mặc dù bạn không phải làm bất cứ điều gì để giải phóng bộ nhớ , bạn vẫn phải giải phóng tài nguyên thông qua Dispose (). Đây là một điều cực kỳ dễ quên khi bạn đang sử dụng WinForms hoặc theo dõi các đối tượng theo bất kỳ cách nào.


2
Khối sử dụng () giải quyết gọn gàng vấn đề này. Bất cứ khi nào bạn thấy một cuộc gọi đến Vứt bỏ, bạn có thể tái cấu trúc ngay lập tức và an toàn để sử dụng bằng cách sử dụng ().
Jeremy Frey

5
Tôi nghĩ rằng mối quan tâm đã được thực hiện IDis Dùng chính xác.
Mark Brackett

4
Mặt khác, thói quen sử dụng () có thể cắn bạn bất ngờ, như khi làm việc với PInvoke. Bạn không muốn loại bỏ thứ gì đó mà API vẫn đang tham chiếu.
MusiGenesis

3
Việc triển khai IDis Dùng một cách chính xác là rất khó và hiểu ngay cả lời khuyên tốt nhất tôi đã tìm thấy về điều này (Nguyên tắc .NET Framework) có thể gây nhầm lẫn để áp dụng cho đến khi cuối cùng bạn "hiểu được".
Quibbledome

1
Lời khuyên tốt nhất tôi từng tìm thấy về
IDis Dùng

19

Mảng thực hiện IList

Nhưng đừng thực hiện nó. Khi bạn gọi Add, nó sẽ cho bạn biết rằng nó không hoạt động. Vậy tại sao một lớp thực hiện một giao diện khi nó không thể hỗ trợ nó?

Biên dịch, nhưng không hoạt động:

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

Chúng tôi có vấn đề này rất nhiều, bởi vì serializer (WCF) biến tất cả các IList thành mảng và chúng tôi gặp lỗi thời gian chạy.


8
IMHO, vấn đề là Microsoft không có đủ giao diện được xác định cho các bộ sưu tập. IMHO, cần có iEnumerable, iMultipassEnumerable (hỗ trợ Đặt lại và đảm bảo nhiều lượt đi sẽ khớp), iLiveEnumerable (sẽ có ngữ nghĩa được xác định một phần nếu bộ sưu tập thay đổi trong quá trình liệt kê - thay đổi có thể hoặc không xuất hiện trong bảng liệt kê, nhưng không nên gây ra kết quả không có thật hoặc ngoại lệ), iRead Indexable, iReadWrite Indexable, v.v.
supercat

@supercat, điều đó sẽ gây nhầm lẫn như địa ngục cho người mới bắt đầu và một số lập trình viên lâu năm. Tôi nghĩ rằng các bộ sưu tập .NET và giao diện của chúng rất thanh lịch. Nhưng tôi đánh giá cao sự khiêm tốn của bạn. ;)
Jordan

@Jordan: Kể từ khi viết những điều trên, tôi đã quyết định rằng một cách tiếp cận tốt hơn sẽ là có cả hai IEnumerable<T>IEnumerator<T>hỗ trợ một Featurestài sản cũng như một số phương pháp "tùy chọn" mà tính hữu ích sẽ được xác định bởi "Tính năng" đã báo cáo. Tuy nhiên, tôi đứng ở điểm chính của mình, đó là có những trường hợp mã nhận được IEnumerable<T>sẽ cần những lời hứa mạnh mẽ hơn là IEnumerable<T>cung cấp. Gọi điện ToListsẽ mang lại một IEnumerable<T>lời hứa duy trì những lời hứa như vậy, nhưng trong nhiều trường hợp sẽ rất tốn kém. Tôi sẽ khẳng định rằng nên có ...
supercat

... một phương tiện mà mã nhận được IEnumerable<T>có thể tạo một bản sao nội dung nếu cần nhưng có thể không làm như vậy một cách không cần thiết.
supercat

Tùy chọn của bạn là hoàn toàn không thể đọc được. Khi tôi thấy một IList trong mã, tôi biết những gì tôi đang làm việc thay vì phải thăm dò một thuộc tính Tính năng. Các lập trình viên muốn quên rằng một tính năng quan trọng của mã là nó có thể được đọc bởi những người không chỉ máy tính. Không gian tên bộ sưu tập .NET không lý tưởng nhưng nó tốt và đôi khi tìm ra giải pháp tốt nhất không phải là vấn đề phù hợp với một nguyên tắc lý tưởng hơn. Một số mã tồi tệ nhất tôi từng làm việc là mã cố gắng phù hợp với DRY một cách lý tưởng. Tôi đã loại bỏ nó và viết lại nó. Đó chỉ là mã xấu. Tôi hoàn toàn không muốn sử dụng khuôn khổ của bạn.
Jordan

18

foreach vòng lặp phạm vi biến!

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

in năm "amet", trong khi ví dụ sau hoạt động tốt

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

11
Điều này về cơ bản tương đương với ví dụ của Jon với các phương thức ẩn danh.
Mehrdad Afshari

3
Lưu ý rằng nó thậm chí còn khó hiểu hơn với foreach trong đó biến "s" dễ trộn hơn với biến có phạm vi. Với các vòng lặp chung, biến chỉ số rõ ràng là cùng một biến cho mỗi lần lặp.
Mikko Rantanen

2
blog.msdn.com/ericlippert/archive/2009/11/12/ và vâng, ước gì biến được đặt trong phạm vi "đúng".
Roman Starkov

2
Điều này đã được sửa trong C # 5 .
Johnbot

Về cơ bản, bạn chỉ in cùng một biến nhiều lần mà không thay đổi nó.
Jordan

18

MS SQL Server không thể xử lý ngày trước năm 1753. Đáng kể, đó là không đồng bộ với DateTime.MinDatehằng số .NET , là 1/1/1. Vì vậy, nếu bạn cố gắng lưu ý, một ngày không đúng định dạng (như gần đây đã xảy ra với tôi trong nhập dữ liệu) hoặc đơn giản là ngày sinh của William the Conqueror, bạn sẽ gặp rắc rối. Không có cách giải quyết tích hợp cho việc này; nếu bạn có khả năng cần phải làm việc với ngày trước năm 1753, bạn cần phải viết cách giải quyết của riêng bạn.


17
Thành thật mà nói tôi nghĩ rằng MS SQL Server có quyền này và .Net sai. Nếu bạn thực hiện nghiên cứu thì bạn sẽ biết rằng những ngày trước năm 1751 trở nên thú vị do thay đổi lịch, ngày bị bỏ qua hoàn toàn, v.v. Hầu hết các RDBM đều có một số điểm bị cắt. Điều này sẽ cung cấp cho bạn một điểm khởi đầu: ancestry.com/learn/library/article.aspx?article=3358
NotMe

11
Ngoài ra, ngày là 1753 .. Đó là lần đầu tiên chúng tôi có một lịch liên tục mà không có ngày nào bị bỏ qua. SQL 2008 đã giới thiệu kiểu dữ liệu Date và datetime2 có thể chấp nhận ngày từ 1/1/01 đến 12/12/9999. Tuy nhiên, so sánh ngày sử dụng các loại đó nên được xem với sự nghi ngờ nếu bạn thực sự so sánh ngày trước 1753.
NotMe

Ồ, phải, 1753, đã sửa, cảm ơn.
Shaul Behr

Có thực sự có ý nghĩa để làm so sánh ngày với ngày như vậy? Ý tôi là, đối với Kênh Lịch sử, điều này rất có ý nghĩa, nhưng tôi không thấy bản thân mình muốn biết ngày chính xác trong tuần mà nước Mỹ được phát hiện.
Camilo Martin

5
Thông qua Wikipedia vào ngày Julian bạn có thể tìm thấy một chương trình cơ bản gồm 13 dòng CALJD.BAS được xuất bản năm 1984 có thể tính toán ngày trở lại khoảng 5000 trước Công nguyên, tính đến những ngày nhuận và những ngày bị bỏ qua vào năm 1753. Vì vậy, tôi không hiểu tại sao "hiện đại "Các hệ thống như SQL2008 sẽ làm tồi tệ hơn. Bạn có thể không quan tâm đến một đại diện ngày chính xác trong thế kỷ 15, nhưng những người khác có thể, và phần mềm của chúng tôi sẽ xử lý việc này mà không có lỗi. Một vấn đề khác là bước nhảy vọt. . .
Roland

18

Lincha bộ nhớ cache khó chịu

Xem câu hỏi của tôi dẫn đến khám phá này, và các blogger đã phát hiện ra vấn đề.

Nói tóm lại, DataContext giữ một bộ đệm của tất cả các đối tượng Linq-to-Sql mà bạn đã từng tải. Nếu bất kỳ ai khác thực hiện bất kỳ thay đổi nào đối với bản ghi mà bạn đã tải trước đó, bạn sẽ không thể lấy dữ liệu mới nhất, ngay cả khi bạn tải lại bản ghi một cách rõ ràng!

Điều này là do một thuộc tính được gọi ObjectTrackingEnabledtrên DataContext, theo mặc định là đúng. Nếu bạn đặt thuộc tính đó thành false, bản ghi sẽ được tải lại mỗi lần ... NHƯNG ... bạn không thể duy trì bất kỳ thay đổi nào đối với bản ghi đó với SendChanges ().

GOTCHA!


Iv chỉ mất một ngày rưỡi (và vô số tóc!) Đuổi theo BUG này ...
phẫu phẫu thuật

Điều này được gọi là xung đột đồng thời và ngày nay nó vẫn là một gotcha mặc dù có một số cách nhất định xung quanh vấn đề này mặc dù chúng có xu hướng hơi nặng tay. DataContext là một cơn ác mộng. Ôi
Jordan

17

Hợp đồng trên Stream.Read là điều mà tôi đã thấy rất nhiều người gặp phải:

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

Lý do là sai Stream.Read sẽ đọc tối đa số byte được chỉ định, nhưng hoàn toàn miễn phí chỉ đọc 1 byte, ngay cả khi 7 byte khác có sẵn trước khi kết thúc luồng.

Nó không giúp gì cho việc này trông rất giống với Stream.Write , được đảm bảo đã ghi tất cả các byte nếu nó trả về không có ngoại lệ. Nó cũng không giúp cho mã trên hoạt động gần như mọi lúc . Và tất nhiên, điều đó không giúp ích gì cho việc không có phương pháp thuận tiện, sẵn sàng để đọc chính xác N byte.

Vì vậy, để cắm lỗ hổng và tăng cường nhận thức về điều này, đây là một ví dụ về một cách chính xác để làm điều này:

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

1
Hoặc, trong ví dụ rõ ràng của bạn : var r = new BinaryReader(stream); ulong data = r.ReadUInt64();. BinaryReader cũng có một FillBufferphương thức ...
jimbobmcgee

15

Sự kiện

Tôi không bao giờ hiểu tại sao các sự kiện là một tính năng ngôn ngữ. Chúng rất phức tạp để sử dụng: bạn cần kiểm tra null trước khi gọi, bạn cần hủy đăng ký (chính mình), bạn không thể tìm ra ai đã đăng ký (ví dụ: tôi đã đăng ký chưa?). Tại sao không phải là một sự kiện chỉ là một lớp học trong thư viện? Cơ bản là chuyên ngành List<delegate>?


1
Ngoài ra, đa luồng là đau đớn. Tất cả những vấn đề này nhưng điều không được sửa trong CAB (có các tính năng thực sự cần được tích hợp vào ngôn ngữ) - các sự kiện được khai báo trên toàn cầu và bất kỳ phương thức nào cũng có thể tự tuyên bố là "thuê bao" của bất kỳ sự kiện nào. Vấn đề duy nhất của tôi với CAB là các tên sự kiện toàn cầu là các chuỗi chứ không phải là enums (có thể được sửa bởi các enum thông minh hơn, giống như Java, vốn đã hoạt động như các chuỗi!) . CAB rất khó để thiết lập, nhưng có một bản sao nguồn mở đơn giản có sẵn ở đây .
BlueRaja - Daniel Pflughoeft

3
Tôi không thích việc thực hiện các sự kiện .net. Đăng ký sự kiện nên được xử lý bằng cách gọi một phương thức thêm đăng ký và trả về IDis Dùng một lần, khi Dispose'd, sẽ xóa đăng ký. Không cần một cấu trúc đặc biệt kết hợp phương thức "thêm" và "loại bỏ" mà ngữ nghĩa của nó có thể hơi khó hiểu, đặc biệt nếu một người cố gắng thêm và sau đó loại bỏ một đại biểu phát đa hướng (ví dụ: Thêm "B" theo sau là "AB", sau đó xóa "B" (để lại "BA") và "AB" (vẫn để lại "BA"). Rất tiếc
supercat

@supercat Bạn sẽ viết lại button.Click += (s, e) => { Console.WriteLine(s); }như thế nào?
Ark-kun

Nếu tôi phải có thể hủy đăng ký riêng với các sự kiện khác IEventSubscription clickSubscription = button.SubscribeClick((s,e)=>{Console.WriteLine(s);});và hủy đăng ký qua clickSubscription.Dispose();. Nếu đối tượng của tôi sẽ giữ tất cả các đăng ký trong suốt cuộc đời của nó, MySubscriptions.Add(button.SubscribeClick((s,e)=>{Console.WriteLine(s);}));và sau đó MySubscriptions.Dispose()để giết tất cả các đăng ký.
supercat

@ Ark-kun: Phải giữ các đối tượng đóng gói bên ngoài đăng ký có vẻ như phiền toái, nhưng liên quan đến đăng ký vì các thực thể sẽ có thể tổng hợp chúng với một loại có thể đảm bảo tất cả chúng đều được dọn sạch, một điều rất khó khăn.
supercat

14

Hôm nay tôi đã sửa một lỗi đã trốn tránh trong một thời gian dài. Lỗi này là trong một lớp chung được sử dụng trong kịch bản đa luồng và trường int tĩnh được sử dụng để cung cấp khóa đồng bộ hóa miễn phí bằng cách sử dụng Khóa liên động. Lỗi được gây ra bởi vì mỗi lần khởi tạo của lớp chung cho một loại có tĩnh riêng. Vì vậy, mỗi luồng có trường tĩnh riêng và nó không được sử dụng khóa như dự định.

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

Cái này in 5 10 5


5
bạn có thể có một lớp cơ sở không chung chung, xác định các số liệu thống kê và kế thừa các tổng quát từ nó. Mặc dù tôi chưa bao giờ rơi vào hành vi này trong C # - Tôi vẫn nhớ thời gian sửa lỗi dài của một số mẫu C ++ ... Eww! :)
Paulius

7
Thật kỳ lạ, tôi nghĩ rằng điều này là rõ ràng. Chỉ cần nghĩ về những gì nó nên làm nếu icó loại T.
Timwi

1
Tham số loại là một phần của Type. SomeGeneric<int>là một loại khác nhau từ SomeGeneric<string>; vì vậy tất nhiên mỗi người có một cái riêngpublic static int i
radarbob

13

Số lượng có thể được đánh giá nhiều lần

Nó sẽ cắn bạn khi bạn có vô số người lười biếng và bạn lặp đi lặp lại hai lần và nhận được kết quả khác nhau. (hoặc bạn nhận được kết quả tương tự nhưng nó thực hiện hai lần không cần thiết)

Ví dụ, trong khi viết một bài kiểm tra nhất định, tôi cần một vài tệp tạm thời để kiểm tra logic:

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

Hãy tưởng tượng sự ngạc nhiên của tôi khi File.Delete(file)ném FileNotFound!!

Điều đang xảy ra ở đây là vô số lầnfiles lặp lại hai lần (kết quả từ lần lặp đầu tiên đơn giản là không được nhớ) và trên mỗi lần lặp mới, bạn sẽ gọi lạiPath.GetTempFilename() để bạn có được một tên tệp tạm thời khác.

Tất nhiên, giải pháp là háo hức liệt kê giá trị bằng cách sử dụng ToArray()hoặc ToList():

var files = Enumerable.Range(0, 5)
    .Select(i => Path.GetTempFileName())
    .ToArray();

Điều này thậm chí còn đáng sợ hơn khi bạn đang làm một cái gì đó đa luồng, như:

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

và bạn tìm ra content.Lengthvẫn là 0 sau khi viết xong !! Sau đó, bạn bắt đầu kiểm tra nghiêm ngặt rằng bạn không có tình trạng chủng tộc khi .... sau một giờ lãng phí ... bạn đã nhận ra rằng đó chỉ là một điều nhỏ bé mà bạn đã quên ....


Đây là do thiết kế. Nó được gọi là thực hiện hoãn lại. Trong số những thứ khác, nó có nghĩa là mô phỏng các cấu trúc TSQL. Mỗi khi bạn chọn từ chế độ xem sql, bạn sẽ nhận được kết quả khác nhau. Nó cũng cho phép xâu chuỗi hữu ích cho các cửa hàng dữ liệu từ xa, chẳng hạn như SQL Server. Nếu không x.Select.Where.OrderBy sẽ gửi 3 lệnh riêng biệt tới cơ sở dữ liệu ...
as9876 14/07/2015

@AYS bạn có bỏ lỡ từ "Gotcha" trong tiêu đề câu hỏi không?
chakrit

Tôi nghĩ gotcha có nghĩa là sự giám sát của các nhà thiết kế, không phải là một cái gì đó có chủ ý.
as9876

Có lẽ nên có một loại khác cho IEnumerables không thể khởi động lại. Giống như, AutoBufferedEnable? Người ta có thể thực hiện nó một cách dễ dàng. Gotcha này dường như chủ yếu là do thiếu kiến ​​thức của lập trình viên, tôi không nghĩ có gì sai với hành vi hiện tại.
Câu hỏi hóc búa Eldritch

13

Chỉ tìm thấy một thứ kỳ lạ khiến tôi bị mắc kẹt trong một thời gian:

Bạn có thể tăng null cho một int nullable mà không cần đưa ra một đoạn trích và giá trị vẫn là null.

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

Đó là kết quả của cách C # tận dụng các hoạt động cho các loại nullable. Nó hơi giống với NaN tiêu thụ tất cả những gì bạn ném vào nó.
IllidanS4 muốn Monica trở lại vào

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

Vâng, hành vi này được ghi lại, nhưng điều đó chắc chắn không làm cho nó đúng.


5
Tôi không đồng ý - khi một từ có trong tất cả các chữ hoa, nó có thể có ý nghĩa đặc biệt rằng bạn không muốn gây rối với Title Case, ví dụ: "tổng thống Hoa Kỳ" -> "President Of The USA", chứ không phải "President Of The Hoa Kỳ".
Shaul Behr

5
@Shaul: Trong trường hợp này, họ nên xác định điều này như một tham số để tránh nhầm lẫn, bởi vì tôi chưa bao giờ gặp bất cứ ai dự kiến hành vi này trước thời hạn - mà làm cho này một Gotcha !
BlueRaja - Daniel Pflughoeft
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.