C # (.NET) Design Flaws [đã đóng]


84

Một số lỗi thiết kế lớn nhất trong C # hoặc .NET Framework nói chung là gì?

Ví dụ: không có kiểu chuỗi không thể null và bạn phải kiểm tra DBNull khi tìm nạp các giá trị từ IDataReader.


Những sai sót trong thiết kế theo nghĩa nào?
— Juliet

Với IDataReader, bạn có thể sử dụng IsDBNull thay vì kiểm tra thủ công
— Marc Gravell

9
Cue Jon Skeet để nói về các lớp học bị phong ấn;)
— johnc

3
Khá dễ dàng để sửa IDataReader bằng một phương thức mở rộng: xem weblogs.asp.net/skillet/archive/2008/06/18/… .
— Robert Rossney

@lagerdalek - Tôi sẽ +1 nhận xét đó nếu có thể; được ghi nhớ nhiều
— Marc Gravell

Câu trả lời:


39

Tôi hoàn toàn đồng ý với bài đăng này (đối với những người đang phân vân vì thiếu ToString, có một thuộc tính trình gỡ lỗi để cung cấp định dạng tùy chỉnh cho lớp của bạn).

Trên đầu danh sách trên, tôi cũng sẽ thêm các yêu cầu hợp lý sau:

  1. loại tham chiếu không thể nullable như một bổ sung cho các loại giá trị có thể null,
  2. cho phép ghi đè phương thức khởi tạo trống của cấu trúc,
  3. cho phép các ràng buộc kiểu chung để chỉ định các lớp được niêm phong,
  4. Tôi đồng ý với một người đăng khác ở đây đã yêu cầu chữ ký hàm tạo tùy ý khi được sử dụng làm ràng buộc, tức là. ở đâu T : new(string), hoặc ở đâuT : new(string, int)
  5. Tôi cũng đồng ý với một người đăng khác ở đây về việc sửa chữa các sự kiện, cho cả danh sách sự kiện trống và trong cài đặt đồng thời (mặc dù sau này rất phức tạp),
  6. toán tử phải được định nghĩa là phương thức mở rộng và không phải là phương thức tĩnh của lớp (hoặc ít nhất không chỉ là phương thức tĩnh),
  7. cho phép các thuộc tính và phương thức tĩnh cho các giao diện (Java có điều này, nhưng C # thì không),
  8. cho phép khởi tạo sự kiện trong trình khởi tạo đối tượng (hiện chỉ cho phép các trường và thuộc tính),
  9. tại sao cú pháp "đối tượng khởi tạo" chỉ có thể sử dụng được khi tạo một đối tượng? Tại sao không làm cho nó có sẵn bất cứ lúc nào, tức là.var e = new Foo(); e { Bar = baz };
  10. sửa chữa hành vi liệt kê bậc hai ,
  11. tất cả các bộ sưu tập phải có ảnh chụp nhanh không thể thay đổi để lặp lại (nghĩa là. việc thay đổi bộ sưu tập sẽ không làm mất hiệu lực của trình lặp),
  12. các bộ giá trị rất dễ thêm vào, nhưng kiểu đại số đóng hiệu quả như " Either<T>" thì không, vì vậy tôi muốn có một số cách để khai báo một kiểu đại số đóng và thực thi đối sánh mẫu đầy đủ trên nó (về cơ bản hỗ trợ hạng nhất cho mẫu khách truy cập, nhưng hiệu quả hơn nhiều); vì vậy chỉ cần lấy enums, mở rộng chúng với hỗ trợ khớp mẫu đầy đủ và không cho phép các trường hợp không hợp lệ,
  13. Tôi muốn hỗ trợ cho việc đối sánh mẫu nói chung, nhưng ít nhất là cho kiểm tra loại đối tượng; Tôi cũng thích cú pháp chuyển đổi được đề xuất trong một bài đăng khác ở đây,
  14. Tôi đồng ý với một bài đăng khác rằng các System.IOlớp, giống như Stream, được thiết kế hơi kém; bất kỳ giao diện nào yêu cầu một số triển khai sẽ NotSupportedExceptionlà một thiết kế tồi,
  15. IListnên đơn giản hơn nhiều so với hiện tại; trên thực tế, điều này có thể đúng đối với nhiều các giao diện bộ sưu tập bê tông, như ICollection,
  16. quá nhiều phương thức tạo ra các ngoại lệ, chẳng hạn như IDictionary,
  17. Tôi muốn một dạng ngoại lệ được kiểm tra tốt hơn dạng có sẵn trong Java (xem phần nghiên cứu về hệ thống kiểu và hiệu ứng để biết cách thực hiện điều này),
  18. sửa các trường hợp góc khó chịu khác nhau trong giải quyết quá tải phương pháp chung; chẳng hạn, hãy thử cung cấp hai phương thức mở rộng được nạp chồng, một phương thức hoạt động trên các kiểu tham chiếu và phương thức kia hoạt động trên các kiểu cấu trúc có thể nullable và xem cách suy luận kiểu của bạn thích điều đó
  19. cung cấp một cách để phản ánh an toàn về trường và tên thành viên cho các giao diện như INotifyPropertyChangedlấy tên trường dưới dạng một chuỗi; bạn có thể làm điều này bằng cách sử dụng một phương thức mở rộng nhận lambda với MemberExpression, tức là. () => Foo, nhưng điều đó không hiệu quả lắm,
    • Cập nhật: C # 6.0 đã thêm nameof()toán tử cho các tên thành viên đơn lẻ, nhưng nó không hoạt động ở dạng chung ( nameof(T) == "T"thay vì tên của đối số kiểu thực tế: bạn vẫn cần làm typeof(T).Name)) - cũng như không cho phép bạn nhận chuỗi "đường dẫn" , ví dụ như nameof(this.ComplexProperty.Value) == "Value"hạn chế các ứng dụng có thể có của nó.
  20. cho phép các toán tử trong các giao diện và thực hiện tất cả các loại số lõi IArithmetic ; các giao diện điều hành được chia sẻ hữu ích khác cũng có thể,
  21. làm cho việc thay đổi các trường / thuộc tính của đối tượng trở nên khó khăn hơn, hoặc ít nhất, cho phép chú thích các trường bất biến và làm cho trình kiểm tra kiểu thực thi nó (chỉ cần coi nó như thuộc tính getter-only fer chrissakes, nó không khó!); trên thực tế, hợp nhất các trường và thuộc tính theo cách hợp lý hơn vì chẳng ích gì khi có cả hai; Các thuộc tính tự động của C # 3.0 là bước đầu tiên theo hướng này, nhưng chúng không đi đủ xa,
    • Cập nhật: Mặc dù C # có readonlytừ khóa và C # 6.0 đã thêm thuộc tính tự động chỉ đọc, mặc dù nó không nghiêm ngặt như hỗ trợ ngôn ngữ thực cho các loại và giá trị bất biến.
  22. đơn giản hóa việc khai báo các hàm tạo; Tôi thích cách tiếp cận của F #, nhưng bài đăng khác ở đây yêu cầu đơn giản là "mới" thay vì tên lớp ít nhất thì tốt hơn,

Bây giờ tôi cho là đủ rồi. Đây là tất cả những khó chịu mà tôi đã gặp phải trong tuần qua. Tôi có lẽ có thể tiếp tục hàng giờ đồng hồ nếu tôi thực sự dành tâm trí cho nó. C # 4.0 đã thêm các đối số được đặt tên, tùy chọn và mặc định, mà tôi thực sự chấp thuận.

Bây giờ cho một yêu cầu không hợp lý:

  1. sẽ thực sự tuyệt vời nếu C # / CLR có thể hỗ trợ tính đa hình của hàm tạo kiểu, tức là. generics hơn generic,

Đẹp không? :-)


1
Liên quan đến # 1, mọi kiểu đều phải có một số giá trị mặc định hoặc hệ thống phải cung cấp phương tiện chạy một hàm tạo bất cứ khi nào một biến hoặc trường của một kiểu cụ thể được cấp phát. Tôi thích cái sau hơn (một nhánh của # 2) nhưng # 1 có thể được đáp ứng nếu các phương thức / thuộc tính không ảo có thể được trang trí để chỉ định rằng chúng nên được gọi mà không có dấu kiểm. Điều này sẽ cho phép những thứ như trường "Chuỗi" hoạt động như thể chúng mặc định là một chuỗi rỗng chứ không phải null (vì hàm "chiều dài" tĩnh của String có thể trả về 0 nếu được gọi trên chuỗi null).
— supercat

1
Liên quan đến # 2, nói chung sẽ hữu ích nếu các cấu trúc không chỉ có thể chỉ định một phương thức khởi tạo không phải là điền-có-không, mà còn là một phương thức khởi tạo sao chép không phải byte-for-byte-copy. Trên thực tế, tôi thực sự muốn thấy một khuôn khổ giống .net có thể làm tốt công việc nhận ra rằng các thực thể có thể có giá trị hoặc ngữ nghĩa tham chiếu và cho phép các đối tượng heap kiểu giá trị được gắn thẻ có thể thay đổi, chia sẻ-không thay đổi hoặc không cam kết (một đối tượng giá trị không được cam kết có thể bị thay đổi nếu chế độ của nó lần đầu tiên là CompareExchange'd thành có thể thay đổi hoặc có thể được chia sẻ nếu chế độ của nó lần đầu tiên là CompareExchange'd được chia sẻ).
— supercat

1
Điểm xuất sắc! Đối với # 1, chú thích hệ thống kiểu là giải pháp phổ biến, nhưng tôi thích tuyên truyền các ràng buộc của hàm tạo thông qua các biến kiểu, như T: new (). Re: # 2, điểm tốt về các hàm tạo bản sao, nhưng tôi sẽ hài lòng với các hàm tạo tổng quát hơn dọc theo các dòng mà tôi đã mô tả ở trên. Thậm chí tốt hơn là loại bỏ hoàn toàn các phương thức khởi tạo-như-phân biệt-và chỉ đơn giản là biến chúng thành các phương thức tĩnh. Điều này cho phép các mẫu xây dựng đơn giản hơn và tổng quát hơn, đặc biệt nếu chúng tôi cho phép các phương thức tĩnh trên các giao diện. Constructors-as-static-method + static-method-in-interface cũng giải quyết # 1.
— naasking

3
# 3: Lợi ích của việc sử dụng lớp niêm phong làm tham số kiểu chung, ví dụ: Foo <T> trong đó T: string là gì? # 11: Được rồi, vậy là tôi đã có List<T>một triệu Ts. Làm thế nào để bạn đề xuất rằng ảnh chụp nhanh được chụp một cách hiệu quả? # 21: Sử dụng readonlytừ khóa .... trong khi có một số gợi ý hay ở đây, chúng hầu như chỉ là - gợi ý, không phải lỗi thiết kế.
— Qwertie

2
Đây là một câu trả lời rất thú vị, nhưng tôi nghĩ chúng ta nên cập nhật các tính năng của C # 6. Ví dụ: Mục 19 và 21 đã được triển khai =)
— eduardobr

72
  • các Reset()phương pháp trên IEnumerator<T>là một sai lầm (đối với khối iterator, spec ngôn ngữ thậm chí đòi hỏi rằng điều này ném một ngoại lệ)
  • Theo quan điểm của Eric, các phương thức phản chiếu trả về mảng là một sai lầm
  • hiệp phương sai của mảng đã và vẫn là một sự kỳ lạ
    • Cập nhật: C # 4.0 với .NET 4.0 đã thêm hỗ trợ hiệp phương sai / tương phản cho các giao diện chung (như IEnumerable<out T>và Func<in T, out TResult>, nhưng không phải các kiểu cụ thể (như List<T>).
  • ApplicationException khá thất vọng - đó có phải là một sai lầm?
  • các bộ sưu tập được đồng bộ hóa - một ý tưởng hay, nhưng không nhất thiết phải hữu ích trong thực tế: bạn thường cần phải đồng bộ hóa nhiều hoạt động ( Containssau đó Add), vì vậy một bộ sưu tập đồng bộ hóa các hoạt động riêng biệt không phải là tất cả đều hữu ích
    • Cập nhật: Các System.Collections.Concurrentloại , với TryAdd, GetOrAdd, TryRemove, vv đã được thêm vào trong .NET Framework 4.0 - mặc dù phương pháp mà chấp nhận một đại biểu nhà máy không đảm bảo nhà máy sẽ chỉ được gọi một lần cho mỗi khoá.
  • có thể sử dụng nhiều hơn đối với using/ lockpattern - có lẽ cho phép chúng chia sẻ cú pháp có thể sử dụng lại (có thể mở rộng?); bạn có thể mô phỏng điều này bằng cách quay lại IDisposablevà sử dụng using, nhưng nó có thể rõ ràng hơn
  • khối lặp: không có cách nào đơn giản để kiểm tra các đối số trước thời hạn (thay vì lười biếng). Chắc chắn, bạn có thể viết hai phương thức chuỗi, nhưng điều đó thật tệ
  • bất biến đơn giản hơn sẽ là tốt; C # 4.0 giúp được một chút , nhưng chưa đủ
  • không hỗ trợ "tham số ref-type này không thể là null" - mặc dù các hợp đồng (trong 4.0) giúp giải quyết phần nào điều này. Nhưng cú pháp như Foo(SqlConnection! connection)(chèn một dấu kiểm / throw) sẽ rất hay (trái ngược vớiint? v.v.)
  • thiếu sự hỗ trợ của các toán tử và các nhà xây dựng không mặc định với generic; C # 4.0 giải quyết vấn đề này một chút với dynamichoặc bạn có thể bật nó như thế này
  • biến trình lặp được khai báo bên ngoài while trongforeach mở rộng, có nghĩa là anon-method / lambdas nắm bắt biến duy nhất, thay vì một biến trên mỗi lần lặp (khó khăn với luồng / async / etc)

! IEnumerable = IEnumerable <object> thực sự là kỳ quặc
— Rauhotz

2
Vâng, điều IEnumerable là một sự nôn nao 1.1; bạn có thể sử dụng .Cast <object> () với LINQ, ít nhất
— Marc Gravell

8
Những người BCL nói rằng đó ApplicationExceptionlà một sai lầm - không hữu ích như họ hy vọng. Họ cũng nói rằng System.Exceptionlẽ ra phải như vậy abstract.
— Jay Bazuzi

2
Không thể nullable: Sẽ là một lỗi biên dịch khi chuyển kiểu tham chiếu thông thường T sang một thứ gì đó nhận T không thể nullable! (giống như bạn không thể chuyển int? sang int). Dĩ nhiên là đi qua hướng khác.
— Jay Bazuzi

1
@Jon Harrop: IMHO, lẽ ra phải có hỗ trợ cho mảng bất biến và cho các tham chiếu mảng chỉ đọc. Có thể đối với một số biến thể mảng khác (ví dụ: "mảng có thể thay đổi kích thước" (tham chiếu gián tiếp) hoặc tham chiếu mảng có giá trị bù và ràng buộc).
— supercat

60

TextWriter là một cơ sở lớp của StreamWriter. wtf?

Điều đó luôn khiến tôi hoang mang đến tột cùng.


19
+1 Tôi phải tra cứu nó mọi lúc. (Whaddaya bình tôi không thể mới TextWriter ()?)
— Nicholas Piasecki

1
Cảm ơn Chúa ... Tôi nghĩ đó chỉ là tôi.
— IJ Kennedy

? Nó luôn luôn là thứ viết văn bản, nhưng chỉ StreamWriter làm như vậy với một luồng. Có vẻ khá đơn giản.
— Jon Hanna

4
Tên StreamWriter không làm cho thực tế là nó viết văn bản đủ IMO. Nghe có vẻ tách biệt giống như nó chỉ viết byte và TextWriter sẽ là một triển khai lạ mắt ở trên cùng có thể chuyển đổi api của (string toWrite) thành byte cho bạn. Bây giờ nếu nó được gọi là StreamTextWriter sau đó chắc chắn, nó sẽ ngay lập tức hiển nhiên, nhưng một chút dài :(
— Quibblesome

44

Một peev nhỏ C # pet - các hàm tạo sử dụng cú pháp C ++ / Java để hàm tạo trùng tên với lớp.

New() hoặc là ctor() sẽ đẹp hơn nhiều.

Và chắc chắn, các công cụ như coderush làm cho việc đổi tên các lớp ít gặp vấn đề hơn, nhưng từ một POV có thể đọc được, New () cung cấp sự rõ ràng tuyệt vời.


Erm, làm thế nào nó biết được bạn đang cố gắng tạo một phiên bản mới?
— BlueRaja - Danny Pflughoeft

4
@BlueRaja: Scott đang đề cập đến việc đặt tên các hàm tạo trong các lớp. class Foo { new(int j) {i = j} int i; }
— dalle

Mặc dù tôi đồng ý 100% rằng ctor () hoặc constructor () sẽ tốt hơn (không phải Newtừ khóa --uppercase là trái với quy ước), tôi ngần ngại gọi nó là một lỗ hổng thiết kế. Họ muốn thu hút các nhà phát triển C ++ / Java hiện có, và việc vay mượn rất nhiều quy ước cú pháp cũ ngu ngốc được cho là đã giúp họ đạt được mục tiêu.
— Qwertie

liên quan đến câu hỏi này: stackoverflow.com/questions/32101993/c-sharp-sorted-linkedlist không có cách nào (chỉ sử dụng .NET) để sắp xếp một cách đơn giản LinkedList bằng MergeSort, bucketsort hoặc bất kỳ thuật toán sắp xếp nào khác nhưng sử dụng linq, cách đó chậm hơn hơn là một triển khai đột xuất.
— CoffeDeveloper

29

Tôi không hiểu rằng bạn không thể làm

trong đó T: mới (U)

Vì vậy, bạn khai báo rằng kiểu chung T có một phương thức khởi tạo không mặc định.

biên tập:

Tôi muốn làm điều này:

public class A 
{
    public A(string text) 
    {

    }
}


public class Gen<T> where T : new(string text) 
{

}

Một kiểu chung khai báo cách bạn sẽ sử dụng các đối tượng của kiểu đó, tức là giao diện của chúng. Hàm tạo là một chi tiết triển khai của giao diện đó, nó không phải là mối quan tâm của người tiêu dùng. Khi bạn cần tạo một cá thể được tham số hóa, hãy sử dụng một nhà máy.
— Bryan Watts

Đối với thông tin, mặc dù nó không thể thực hiện kiểm tra thời gian biên dịch, nhưng có một số mã trong MiscUtil để sử dụng các hàm tạo không mặc định (ở dạng chung) một cách hiệu quả, tức là không có Activator.CreateInstance hoặc phản chiếu.
— Marc Gravell

Bởi vì nó không có ý nghĩa và gây nhầm lẫn trên một số mục đích sử dụng. Tuy nhiên, nó có thể hữu ích khi làm việc với các đối tượng bất biến.
— Pop Catalin

7
Nói chung, thiếu các ràng buộc thành viên là điều khó chịu, vâng.
— MichaelGG

3
amen, tôi luôn muốn điều này
— Steve

20

Tôi thực sự ngạc nhiên khi tôi là người đầu tiên đề cập đến vấn đề này:

Tập dữ liệu được nhập ADO.NET không hiển thị các cột có thể làm trống dưới dạng thuộc tính của các kiểu có thể vô hiệu. Bạn sẽ có thể viết điều này:

int? i = myRec.Field;
myRec.Field = null;

Thay vào đó, bạn phải viết điều này, điều này thật ngu ngốc:

int? i = (int?)myRec.IsFieldNull() ? (int?)null : myRec.Field;
myRec.SetFieldNull();

Điều này đã gây khó chịu trong .NET 2.0 và thậm chí còn khó chịu hơn khi bạn phải sử dụng jiggery-pokery như trên trong các truy vấn LINQ gọn gàng đẹp mắt của mình.

Nó cũng gây khó chịu khi Add<TableName>Rowphương thức được tạo ra tương tự như không thể hiểu được với khái niệm về kiểu nullable. Hơn thế nữa kể từ khi tạoTableAdapter phương thức không phải như vậy.

Không có nhiều thứ trong .NET khiến tôi cảm thấy như nhóm nhà phát triển đã nói "Được rồi, các chàng trai, chúng ta đủ gần rồi - hãy giao hàng đi!" Nhưng điều này chắc chắn có.


Tôi hết lòng đồng ý! Điều này làm tôi khó chịu mỗi khi tôi phải sử dụng nó (thường xuyên). Argh! +1
— Eyvind

2
Điều ít nhất họ có thể làm là tạo ra một lớp DataSetV2 mới (tên xấu - chỉ vì lợi ích của đối số), sử dụng các kiểu giá trị nullable thay vì DBNull xuyên suốt.
— Christian Hayter

Đừng quên sự vô lý của việc yêu cầu một giá trị đặc biệt DBNull.Value, khi nullbản thân nó đã hoàn toàn đủ để đại diện cho NULL. May mắn thay, LINQ-to-SQL chỉ sử dụng null cho NULL.
— Qwertie

Trên thực tế, sự vô lý đó là tảng đá mà toàn bộ dinh thự vô lý được xây dựng trên đó.
— Robert Rossney

20
  1. Tôi không phải là một fan hâm mộ lớn của các lớp Stream, StringWriter, StringReader, TextReader, TextWriter ... nó chỉ là không trực quan là gì.
  2. IEnumerable.Reset ném một ngoại lệ cho các trình vòng lặp. Tôi có một số thành phần của bên thứ ba luôn gọi đặt lại khi lập dữ liệu, yêu cầu tôi truyền đến một danh sách trước tiên để sử dụng chúng.
  3. Xml Serializer phải có các phần tử IDictionary được tuần tự hóa
  4. Tôi hoàn toàn quên mất API HttpWebRequest & FTP là một nỗi đau trong .... của tôi (cảm ơn vì đã nhận xét Nicholas nhắc tôi về điều này :-)

Chỉnh sửa
5. Một điều khó chịu khác của tôi là cách System.Reflection.BindingFlags, có các cách sử dụng khác nhau tùy thuộc vào phương pháp bạn sử dụng. Ví dụ trong FindField, CreateInstance hoặc SetField nghĩa là gì? Đây là một trường hợp mà họ đã quá tải ý nghĩa đằng sau cách liệt kê này gây nhầm lẫn.


1
+1 Tôi phải tra cứu bất kỳ lớp XmlTextWriter, TextWriter, v.v. nào mỗi lần. Tương tự với nội dung HttpWebRequest / Response. API hoàn toàn không trực quan ở đó.
— Nicholas Piasecki

+ 1-1 = 0: XmlTextWriter, v.v., tôi đồng ý rằng không thể suy luận chính xác chúng là gì từ tên. HttpWebRequest Tôi không đồng ý, tôi thấy nó khá trực quan.
— AnthonyWJones

Với mỗi người của mình, tôi cho rằng. Tôi đoán với FTP, tôi sẽ mong đợi một mức trừu tượng cao hơn so với những gì họ có.
— JoshBerke

15

Tôi không biết rằng tôi muốn nói rằng đó là một lỗi thiết kế, nhưng sẽ thực sự tuyệt vời nếu bạn có thể suy ra một biểu thức lambda theo cách giống như bạn có thể làm trong VB:

VB:

Dim a = Function(x) x * (x - 1)

C #

Thật tuyệt nếu có thể làm được điều này:

var a = x => x * (x - 1);

Thay vì phải làm điều này:

Func<int, int> a = x => x * (x - 1);

Tôi nhận ra không còn bao lâu nữa, nhưng trong Code Golf mọi nhân vật đều được tính là chết tiệt! Họ không tính đến điều đó khi thiết kế các ngôn ngữ lập trình này sao? :)


3
Microsoft có nên xem xét Code Golf khi thiết kế ngôn ngữ?
— jrcs3

3
@Ray Burns: Làm sao nó biết được trong VB? VB hỗ trợ nó, vậy sự khác biệt là gì?
— BenAlabaster

3
@RayBurns Loại suy luận? Tôi đã sử dụng nó từ năm 1989.
— RD1

3
Lamdas là Homoiconic trong C #. mảnh vỡ (int x) => x * (x -1);có thể có nghĩa Func<int, int>hoặc nó có thể có nghĩaExpression<Func<int, int>>
— Scott Weinstein

3
@BenAlabaster: VB hỗ trợ toán tử số học bị ràng buộc muộn. C # phải giải quyết chúng tại thời điểm biên dịch. Đó là sự khác biệt về ngôn ngữ. Ví dụ VB có thể thêm hai đối tượng với nhau. C # không thể vì +không được định nghĩa cho object.
— đệ quy

14
  1. Lớp System.Object :

    • Equals và GetHashCode - không phải tất cả các lớp đều có thể so sánh được hoặc có thể băm, nên được chuyển sang một giao diện. IEquatable hoặc ICompABLE (hoặc tương tự) xuất hiện trong tâm trí.

    • ToString - không phải tất cả các lớp đều có thể được chuyển đổi thành chuỗi, nên được chuyển sang một giao diện. IFormattable (hoặc tương tự) xuất hiện trong tâm trí.

  2. Các ICollection.SyncRoot bất động sản:

    • Khuyến khích thiết kế kém, một ổ khóa bên ngoài hầu như luôn hữu ích hơn.
  3. Generics đáng lẽ phải có ngay từ đầu:

    • Không gian tên System.Collections chứa rất nhiều lớp và giao diện đã lỗi thời.

1
1. Những phương thức đó phổ biến đến mức người ta quyết định rằng các trường hợp kỳ quặc là ok, liệu ai đó có thể gọi Object.Equals trên lớp của bạn không? Được biết rằng có thể có hoặc có thể không có triển khai và yêu cầu: IEquatable, IFormattable trên 99% các lớp là kỳ quặc.
— Guvante

1
Sẽ rất hữu ích nếu bạn có thể xây dựng một từ điển với các đối tượng do người dùng xác định làm khóa, sử dụng bình đẳng tham chiếu mặc định, mà không cần phải thêm mã rõ ràng vào các đối tượng do người dùng xác định cho mục đích đó. Tôi sẽ coi Finalize là một sự lãng phí lớn hơn nhiều (một giải pháp thay thế tốt hơn sẽ là có các đối tượng cần hoàn thiện triển khai iFinalizable và tự đăng ký rõ ràng để hoàn thiện). OTOH, lẽ ra phải có nhiều hỗ trợ cố hữu hơn cho iDisposable, bao gồm cả việc gọi Dispose nếu một hàm tạo ném ra một ngoại lệ.
— supercat

1
@supercat: Tất cả những gì cần thiết là cập nhật EqualityComparer<T>.Defaultchính xác. Sau đó, cả hai var dict = new Dictionary<object, string>(EqualityComparer<object>.Default)và var dict = new Dictionary<object, string>()sẽ sử dụng tham chiếu so sánh / bình đẳng.
— dalle

1
@supercat: Những gì bạn mô tả là chính xác những gì EqualityComparer<T>.Defaulthiện. Không cần phải kiểm tra mỗi lần tra cứu. Trình so sánh là một thuộc tính cho Dictionaryví dụ và mỗi người Dictionarybiết nó đang sử dụng thuộc tính nào.
— dalle

1
@supercat: Một từ điển phải sử dụng loại chung nhất (tức là lớp cơ sở chung) làm khóa, việc sử dụng cả Chuỗi và Thời gian ngày trong cùng một từ điển sẽ không tạo ra bất kỳ điều gì trừ khi sử dụng so sánh tham chiếu, trừ khi so sánh do người dùng xác định được dẫn đến Là. Hãy nhớ rằng tên của chủ đề này là "C # (.NET) Design Flaws".
— dalle

12

Một trong những điều làm tôi khó chịu là Predicate<T> != Func<T, bool>nghịch lý. Cả hai đều thuộc loại đại biểu T -> boolnhưng chúng không tương thích với nhiệm vụ.


Có một thủ thuật sử dụng Delegate.Create và một số đúc để làm việc chuyển đổi, nhưng ít nhất là có thể làm một diễn viên rõ ràng sẽ được tốt đẹp (Tôi có thể hiểu được sự thiếu hỗ trợ cho tiềm ẩn tuy nhiên)
— Guvante

Thiết kế của các đại biểu nói chung là thiếu sót; ví dụ: thiếu sự kiện yếu (triển khai các sự kiện yếu phía nguồn mà không có nỗ lực đặc biệt từ người đăng ký chỉ có thể được thực hiện với một loạt các phản ánh và ReflectionPermission, xem codeproject.com/Articles/29922/Weak-Events-in-C ), và sự kém hiệu quả xuất phát từ yêu cầu rằng các đại biểu phải là các loại tham chiếu (các đại biểu sẽ nhanh hơn và sẽ sử dụng 1/3 bộ nhớ trong nhiều trường hợp, nếu chúng là các loại giá trị - thì chúng sẽ đơn giản là một cặp gợi ý rằng bạn có thể vượt qua trên stack).
— Qwertie

11

Một số người (ISV) muốn rằng bạn có thể biên dịch nó thành mã máy tại thời điểm xây dựng và liên kết nó, để tạo một tệp thực thi gốc không cần thời gian chạy dotNet.


Bạn có thể NGEN chương trình của mình trước khi chạy.
— Otávio Décio

Không giống như loại bỏ các phụ thuộc của thời gian chạy. Bạn chỉ lưu lần chạy JIT đầu tiên trên mã của bạn.
— Ed S.


Không có một công cụ obfuscation nào thực hiện điều này? Nó sẽ nhúng khung công tác vào exe của bạn để bạn không phải triển khai nó. Tôi không thể rember nó tên ... nó không phải là ưu tiên của ...
— JoshBerke

2
Postbuild của Xenocode không làm được điều đó sao? Nó sẽ được tốt đẹp nếu Visual Studio đã có một cách để làm điều này ...
— BenAlabaster

11

Chúng tôi biết rất nhiều về các kỹ thuật OO phù hợp . Tách rời, lập trình theo hợp đồng, tránh kế thừa không đúng, sử dụng thích hợp các ngoại lệ, chính mở / đóng, khả năng thay thế Liskov, v.v. Tuy nhiên, các khung .Net không sử dụng các phương pháp hay nhất.

Đối với tôi, khuyết điểm lớn nhất trong thiết kế của .Net là không đứng trên vai của những người khổng lồ; quảng bá các mô hình lập trình ít hơn lý tưởng cho nhiều lập trình viên sử dụng khuôn khổ của họ .

Nếu MS chú ý đến điều này, thế giới kỹ thuật phần mềm có thể đã có những bước tiến nhảy vọt về chất lượng, độ ổn định và khả năng mở rộng trong thập kỷ này, nhưng than ôi, nó dường như đang thoái trào.


4
+1 cho rant đúng mục tiêu; tôi sẽ thêm vào này không có khả năng phân lớp mỗi lớp, thiếu giao diện cho các lớp học cơ bản, và sự miễn cưỡng để lỗi khuôn khổ sửa chữa thậm chí sau nhiều năm
— Steven A. Lowe

1
Nếu chúng ta cố gắng theo đuổi, có phải chúng ta đang nói rằng các framework .Net quá tệ đến mức thật khó để giải quyết lỗ hổng nào là tệ nhất? Tôi thực sự cảm thấy tốt hơn sau khi xả hơi và đánh giá cao sự ủng hộ vì tôi đã mong đợi được các chàng trai hâm mộ MS hét lên.
— Daniel Paull

Tôi cũng không bỏ phiếu theo cách nào cả, và dù sao thì tôi cũng rất miễn cưỡng đưa ra những phiếu bầu thấp, nhưng tôi đang cố gắng tìm ra lý do tại sao tôi CHỈ KHÔNG CHĂM SÓC.
— Mike Dunlavey

2
Tôi nghĩ bạn đang nói "nó không tốt như nó có thể", đó là một câu trả lời không phải. Không có gì là hoàn hảo. Cung cấp chi tiết cụ thể.
— jcollum

3
Không, tôi đang nói rằng có rất nhiều trường hợp thiết kế rõ ràng là thiếu sót, sau đó khi bạn nghĩ rằng họ làm đúng, họ vẫn làm sai. Ví dụ: bài đăng của tôi lên các diễn đàn MSDN ở đây: social.msdn.microsoft.com/forums/en-US/wpf/thread/…
— Daniel Paull,

11

Tôi không thích câu lệnh switch C #.

Tôi muốn một cái gì đó như thế này

switch (a) {
  1    : do_something;
  2    : do_something_else;
  3,4  : do_something_different;
  else : do_something_weird; 
}

Vì vậy, không còn dấu ngắt (dễ quên) và khả năng phân tách các giá trị khác nhau bằng dấu phẩy.


Trên thực tế, tôi nghĩ sẽ còn tốt hơn nếu nó yêu cầu một câu lệnh đơn hoặc một khối trong dấu ngoặc nhọn - giống như mọi thứ khác trong C #. Nếu không có khả năng vượt qua, cú pháp ngắt hiện tại hơi khó hiểu và nó cũng không giới hạn phạm vi. (AFAIK, nó có thể, tôi không biết)
— Tamas Czinege

Tôi không hiểu ý của bạn. Đăng tuyên bố chuyển đổi 'lý tưởng' của riêng bạn ở đây. Tôi không muốn rơi qua các giá trị được phân tách bằng dấu phẩy.
— tuinstoel

8
Nhân tiện, tôi đồng ý với OP. switchvề cơ bản bị hỏng trong tất cả các ngôn ngữ mô phỏng phiên bản cố ý làm tê liệt của C (được tối ưu hóa cho tốc độ!). VB có giá tốt hơn nhiều, nhưng vẫn chậm hơn nhiều năm so với các ngôn ngữ có khớp mẫu (Haskell, F #…).
— Konrad Rudolph

1
tuinostel: một cái gì đó giống như switch (a) {case 1 {do_something; } case 2 {do_something_else; }} - có nghĩa là, nhận được thoát khỏi lệnh break và đòi hỏi khối mã thích hợp cho từng trường hợp
— Tamas Czinege

2
Nói chung có break dường như là một lỗi, nó không phải là một lỗi biên dịch để phát ra nó? Nó dường như chỉ ở đó để dễ dàng chuyển đổi từ C sang C # (hay còn gọi là dạy các nhà phát triển mà họ không thể tự động vượt qua)
— Guvante 11/02/09

10

Sự kiện trong C #, nơi bạn phải kiểm tra rõ ràng người nghe. Đó chẳng phải là điểm của các sự kiện, để phát sóng cho bất cứ ai tình cờ có mặt ở đó? Ngay cả khi không có bất kỳ?


1
Đó là gây phiền nhiễu mà không có đường cho điều này khi bạn muốn nó, tôi thấy lý do tại sao họ làm điều đó khó khăn, nó khuyến khích không instantiating đối số của sự kiện nếu không cần thiết
— ShuggyCoUk

Hừm. Tôi không hiểu hoặc không đồng ý, hoặc cả hai :-). Tôi không thể nói rằng tôi đã từng cảm thấy chán nản khi bắt đầu bất cứ điều gì. Điều này có vẻ giống như sự tối ưu hóa quá sớm đối với tôi.
— Thomas Eyde

Trong một số trường hợp, phương pháp từng phần là một giải pháp thay thế khả thi.
— Robert Harvey

CỘNG tất cả các khớp nối này gây ra ... MS có CAB khắc phục cả hai vấn đề này, nhưng CAB có nhiều vấn đề của riêng nó do các hạn chế trong C # (ví dụ: chuỗi thay vì enums như chủ đề sự kiện) - tại sao không chỉ tạo một cách lỏng lẻo -các sự kiện ghép đôi một phần của ngôn ngữ !?
— BlueRaja - Danny Pflughoeft

9

Hành vi khủng khiếp (và khá vô hình đối với hầu hết mọi người) O (N ^ 2) của các trình lặp đệ quy / đệ quy lồng nhau .

Tôi khá buồn khi họ biết về nó, biết cách sửa chữa nó nhưng nó không được coi là có đủ ưu tiên để đưa vào danh sách.

Tôi luôn làm việc với các cấu trúc dạng cây và phải sửa lại mã của những người thông minh khi họ vô tình đưa ra các hoạt động đắt tiền theo cách này.

Cái hay của "gain foreach" là cú pháp đơn giản hơn, dễ dàng hơn khuyến khích mã chính xác, hiệu quả. Đây là "hố sâu thành công" mà tôi nghĩ họ nên khao khát trước khi thêm các tính năng mới cho thành công lâu dài của nền tảng.


Điều này đặc biệt sai lầm, bởi vì nó đi ngược lại sự mong đợi trực quan của ans O hành vi như vậy sẽ bẫy nhiều người
— oefe

7

Một số lớp triển khai giao diện nhưng chúng không triển khai nhiều phương thức của giao diện đó, ví dụ: Mảng thực hiện IList nhưng 4 trong số 9 phương thức ném NotSupportedException http://msdn.microsoft.com/en-us/library/system.array_members .aspx


Chà, bạn không thể thay đổi số lượng phần tử trong Mảng, vì vậy, không thể làm gì Thêm, Xóa, Chèn và Xóa (Tại) ngoài việc ném NotSupported ... Thực tế, tôi mong đợi BẤT KỲ việc triển khai IList nào trả về true cho IsFixedSize sẽ ném vào chúng.
— CB

@CB hơi muộn với bữa tiệc, tôi đoán :) Nhưng nếu Array không thể đáp ứng "IList", tại sao vẫn thực hiện nó? Đó là vi phạm nguyên tắc L trong SOLID.
— Winger Sendon

7

Các thành viên tĩnh và các kiểu lồng nhau trong giao diện.

Điều này đặc biệt hữu ích khi một thành viên giao diện có một tham số thuộc loại cụ thể cho giao diện ( ví dụ: an enum). Sẽ rất tuyệt nếu lồng kiểu enum vào kiểu giao diện.


1
Điều này có giống với gợi ý khác của bạn không?
— RCIX

1
Không, cái này là về ngôn ngữ C # và cái kia là về framework. Không phải ai cũng quan tâm đến sự khác biệt, vì vậy hãy để tôi nói rằng cái này là về những gì được cho phép, và cái kia là về những gì được cung cấp.
— Jay Bazuzi

6

Tính chất mặc định nguy hiểm khủng khiếp của các sự kiện. Thực tế là bạn có thể gọi một sự kiện và ở trong trạng thái không nhất quán do người đăng ký bị xóa thật là kinh khủng. Xem các bài báo xuất sắc của Jon Skeet và Eric Lippert để đọc thêm về chủ đề này.


Tôi sẽ không phiền nếu các sự kiện không an toàn theo chuỗi theo mặc định (nó có thể tăng hiệu suất trong mã đơn luồng); Điều ngớ ngẩn là thêm / xóa là an toàn theo mặc định, nhưng cách tự nhiên để kích hoạt một sự kiện là không an toàn và không có cơ sở để dễ dàng làm cho nó an toàn.
— Qwertie

@Qwertie: Điều tồi tệ hơn là trong một thời gian dài, việc thêm / xóa sẽ sử dụng khóa và vẫn không an toàn cho luồng.
— supercat

6
  • null mọi nơi.

  • const hư không.

  • Các API không nhất quán, ví dụ: thay đổi một mảng sẽ trả về voidnhưng thêm vào một StringBuffertrả về cùng một biến StringBuffer.

  • Giao diện bộ sưu tập không tương thích với cấu trúc dữ liệu bất biến, ví dụ: Addin System.Collections.Generic.IList<_>không thể trả về kết quả.

  • Không có cấu trúc gõ để bạn viết System.Windows.Media.Effects.SamplingMode.Bilinearthay vì chỉ Bilinear.

  • Có thể thay đổi IEnumeratorgiao diện được thực hiện bởi các lớp học khi nó phải là một bất biến struct.

  • Bình đẳng và so sánh là một mớ hỗn độn: bạn đã có System.IComparablevà Equalsnhưng sau đó bạn cũng đã có System.IComparable<_>, System.IEquatable, System.Collections.IComparer, System.Collections.IStructuralComparable, System.Collections.IStructuralEquatable, System.Collections.Generic.IComparervà System.Collections.Generic.IEqualityComparer.

  • Tuples phải là cấu trúc nhưng cấu trúc ngăn cản việc loại bỏ lệnh gọi đuôi một cách không cần thiết, vì vậy một trong những kiểu dữ liệu cơ bản và phổ biến nhất sẽ phân bổ không cần thiết và phá hủy tính song song có thể mở rộng.


Không có kiểu gõ cấu trúc trong CLR, nhưng bạn dường như gõ cấu trúc lẫn lộn với kiểu suy luận hoặc với tính năng Ruby được gọi là "ký hiệu". Kiểu nhập cấu trúc sẽ là nếu CLR coi Func <int, bool> và Predicate <int> là cùng một kiểu hoặc ít nhất là có thể chuyển đổi hoàn toàn.
— Qwertie

Nhắc đến so sánh, đừng quên Comparer <T>!
— Qwertie

@Qwertie Tôi đang đề cập đến các tính năng của ngôn ngữ lập trình như các biến thể đa hình trong OCaml. Thư viện LablGL của OCaml có nhiều ví dụ thú vị về việc nhập cấu trúc hữu ích trong ngữ cảnh đồ họa. Không liên quan gì đến suy luận kiểu và chỉ liên quan tiếp tuyến với các ký hiệu.
— JD

1
Làm thế nào để sử dụng một cấu trúc bất biến IEnumerator?
— supercat

5

0 moonlighting as enum

đặc thù của enum: http://blogs.msdn.com/abhinaba/archive/2007/01/09/more-peculiarites-of-enum.aspx

như được minh họa bởi ví dụ điển hình này: http://plus.kaist.ac.kr/~shoh/postgresql/Npgsql/apidocs/Npgsql.NpgsqlParameterCollection.Add_overload_3.html

đề xuất của tôi, hãy đặt dấu "@" để sử dụng tốt:

thay vì:

if ((myVar & MyEnumName.ColorRed)! = 0)

dùng cái này:

if ((myVar & MyEnumName.ColorRed)! = @ 0)


1
1 enum là một trong số ít những điều Java đã làm đúng khi C # không
— BlueRaja - Danny Pflughoeft

5

Để thêm vào danh sách dài những điểm tốt của những người khác đã có:

  • DateTime.Now == DateTime.Now trong hầu hết, nhưng không phải tất cả các trường hợp.

  • Stringlà bất biến có một loạt các tùy chọn để xây dựng và thao tác, nhưng StringBuilder(có thể thay đổi) thì không.

  • Monitor.Entervà Monitor.Exitlẽ ra phải là các phương thức thể hiện, vì vậy thay vì tạo mới một đối tượng cụ thể để khóa, bạn có thể tạo mới a Monitorvà khóa đối tượng đó.

  • Trình hủy không bao giờ được đặt tên là trình hủy. Đặc tả ECMA gọi chúng là trình hoàn thiện, điều này ít gây nhầm lẫn hơn cho đám đông C ++, nhưng đặc tả ngôn ngữ vẫn gọi chúng là trình hủy.


3
Các DateTime.Nowmột là rõ ràng cuộc đua điều kiện nhất thế giới, nhưng 1 cho phần còn lại
— BlueRaja - Danny Pflughoeft

Nó không phải là một điều kiện chủng tộc, nhưng thực tế là họ đã biến nó thành một tài sản. Các thuộc tính trông giống hệt như các trường, vì vậy nó là hành vi khá đáng ngạc nhiên IMO.
— Brian Rasmussen

4
@Brian Rasmussen: DateTime.Now là một thuộc tính khá đúng đắn, vì nó được thay đổi không phải bằng cách đọc nó mà bởi các yếu tố bên ngoài. Nếu một người đọc một thuộc tính như SomeForm.Width, và sau đó - sau khi người dùng đã thay đổi kích thước biểu mẫu - một người đọc lại nó, thì giá trị trong lần đọc thứ hai sẽ khác. Mặc dù có thể DateTime.Now đầu tiên có thể mất đủ thời gian để thực thi nó sẽ ảnh hưởng đến giá trị được đọc bởi thứ hai, hiệu ứng đó sẽ không khác bất kỳ hàm nào khác có cùng một khoảng thời gian thực thi.
— supercat

4

Cách chúng tôi sử dụng các thuộc tính đôi khi làm tôi khó chịu. Tôi thích nghĩ chúng tương đương với các phương thức getFoo () và setFoo () của Java. Nhưng không phải vậy.

Nếu Nguyên tắc sử dụng thuộc tính nêu rõ rằng các thuộc tính có thể được đặt theo bất kỳ thứ tự nào để quá trình tuần tự hóa có thể hoạt động, thì chúng sẽ vô dụng đối với việc xác thực thời gian thiết lập. Nếu bạn đến từ một nền mà bạn muốn ngăn một đối tượng không cho phép chính nó rơi vào trạng thái không hợp lệ, thì thuộc tính không phải là giải pháp của bạn. Đôi khi tôi không thể thấy họ tốt hơn các thành viên công cộng như thế nào, vì chúng tôi rất hạn chế về những loại công việc mà chúng tôi phải làm trong tài sản.

Để đạt được điều đó, tôi đã luôn ước gì (điều này chủ yếu là suy nghĩ lung tung ở đây, tôi chỉ ước mình có thể làm được điều gì đó như thế này) rằng tôi có thể mở rộng cú pháp thuộc tính bằng cách nào đó. Hãy tưởng tượng một cái gì đó như thế này:


private string password;

public string Password
{
    // Called when being set by a deserializer or a persistence
    // framework
    deserialize
    {
       // I could put some backward-compat hacks in here. Like
       // weak passwords are grandfathered in without blowing up
       this.password = value;
    }
    get
    {
       if (Thread.CurrentPrincipal.IsInRole("Administrator"))
       {
           return this.password;
       }
       else
       {
           throw new PermissionException();
       }
    }
    set
    {
       if (MeetsPasswordRequirements(value))
       {
           throw new BlahException();
       }
       this.password = value;
    }
    serialize
    {
        return this.password;
    }
}

Tôi không chắc liệu điều đó có hữu ích hay nó truy cập những thứ đó sẽ trông như thế nào. Nhưng tôi chỉ ước rằng tôi có thể làm được nhiều hơn với các thuộc tính và thực sự coi chúng như các phương thức get và set.


3
Tôi tin rằng họ cung cấp giao diện ISerializable và hàm tạo ngầm để làm những việc như vậy, hay còn gọi là khi bạn không muốn trình tuần tự chỉ gọi các thuộc tính. Trong khi nhiều công việc hơn một chút, có vẻ như bạn đã làm hầu hết công việc đó với phương pháp của mình.
— Guvante

4

Các phương pháp mở rộng rất hay nhưng chúng là một cách xấu xí để giải quyết các vấn đề mà lẽ ra có thể được giải quyết sạch hơn với các mixin thực (hãy nhìn vào ruby ​​để xem tôi đang nói gì), về chủ đề mixin. Một cách thực sự hay để thêm chúng vào ngôn ngữ là cho phép các generic được sử dụng để kế thừa. Điều này cho phép bạn mở rộng các lớp hiện có theo hướng đối tượng tốt:

public class MyMixin<T> : T
{
    // etc...
}

điều này có thể được sử dụng như thế này để mở rộng một chuỗi, ví dụ:

var newMixin = new MyMixin<string>();

Nó mạnh hơn nhiều so với các phương thức mở rộng vì nó cho phép bạn ghi đè các phương thức, chẳng hạn như bọc chúng cho phép chức năng giống AOP bên trong ngôn ngữ.

Xin lỗi vì rant :-)


5
Thật thú vị, nhưng tôi thích các phương pháp mở rộng hơn. Nếu tôi nhận được một thư viện có chứa một loạt các phương thức mở rộng cho chuỗi, tôi không muốn phải thay đổi tất cả các tham chiếu chuỗi thành MyMixin <string> để có được nội dung mới. Đó là một phần nhỏ, chắc chắn, nhưng việc bổ sung các phương thức minh bạch là điều khiến các phương pháp mở rộng trở nên tốt đẹp.
— RCIX

BTW bạn có biết rằng nó hoạt động rồi?
— RCIX

2
Tôi không thấy như thế nào LINQ có thể làm việc theo cách này
— BlueRaja - Danny Pflughoeft

2
@RCIX: Mixin giống như cách tôi nghĩ rằng các phương pháp mở rộng sẽ hoạt động. Vấn đề với việc làm cho các phương thức mở rộng là ngầm định là nó có nghĩa là các thành viên lớp thực sự cần được ưu tiên hơn các phương thức mở rộng. Nếu một phương thức mở rộng Graphics.DrawParallelogram (Pen p, Point v1, Point v2, Point v3) được xác định và sau đó một hàm DrawParallelogram được thêm vào System.Graphics sử dụng các điểm theo một thứ tự khác, mã sử dụng phương thức mở rộng sẽ bị hỏng mà không cảnh báo. BTW, sẽ đã có bất kỳ vấn đề sử dụng hai chấm cho các phương pháp mở rộng (ví dụ object..method ()?)
— supercat

3

Microsoft sẽ không sửa các lỗi rõ ràng trong khuôn khổ và sẽ không cung cấp các móc để người dùng cuối có thể sửa chúng.

Ngoài ra, không có cách nào để thực thi .NET nhị phân vá lỗi trong thời gian chạy và không có cách nào để chỉ định phiên bản riêng của thư viện khung công tác .NET mà không có bản vá nhị phân các thư viện gốc (để chặn cuộc gọi tải) và ILDASM không thể phân phối lại nên tôi không thể tự động hóa bản vá.


1
Ý bạn là những lỗi khung rõ ràng nào?
— Robert Rossney

1
# 1 Nhấp vào điều khiển con có thể nhìn thấy một phần của điều khiển có thể cuộn. Điều khiển được chuyển sang chế độ xem trước khi nó nhận được sự kiện MouseDown, khiến lần nhấp chuột ở một nơi khác trên điều khiển hơn mong đợi. Nó tệ hơn ở chế độ xem dạng cây, nơi nó cũng kích hoạt hoạt động kéo.
— Joshua


3
  • Có thể gọi một phương thức mở rộng trên biến null được cho là có thể

    đối tượng a = null; a.MyExtMethod (); // cái này có thể gọi được, giả sử ở đâu đó nó đã định nghĩa MyExtMethod

    Nó có thể hữu ích nhưng nó không rõ ràng về các chủ đề ngoại lệ tham chiếu rỗng.

  • Một 'lỗ hổng' đặt tên. 'C' của "cấu hình" trong System.configuration.dll phải được viết hoa.

  • Xử lý ngoại lệ. Ngoại lệ nên được bắt hoặc ném ra giống như trong Java, trình biên dịch nên kiểm tra nó tại thời điểm biên dịch. Người dùng không nên dựa vào các nhận xét để biết thông tin ngoại lệ trong lời gọi đích.


3
Tuy nhiên, rất tiện dụng - Tôi có một phương thức mở rộng "ThrowIfNull` để kiểm tra tham số ;-p
— Marc Gravell

2
bạn có thể làm được việc này? ugh ThrowIfNull nó là tiện ích mở rộng thú vị nhưng điều này có vẻ sai.
— JoshBerke

1
Trong CLR thuần túy, bạn có thể Gọi các phương thức cá thể trên tham chiếu null và nếu phương thức không truy cập đối tượng hoặc các trường của nó thì cuộc gọi sẽ không ném ra ngoại lệ tham chiếu null. (Bạn không thể làm điều này trong C # như nó sử dụng callvirt ngay cả đối với các phương pháp không ảo)
— Pop Catalin

7
điều ngoại lệ là sai lầm. Bạn không thể thất bại nhanh chóng nếu bạn phải nắm bắt mọi ngoại lệ chết tiệt có thể bị ném vào ngăn xếp cuộc gọi. Nhưng tôi muốn nó dễ dàng hơn nhiều để xác định bất kỳ và tất cả các ngoại lệ có thể được đưa ra trong một cuộc gọi cụ thể và cuộc gọi kết quả của nó ...

3
@Will: Xử lý ngoại lệ là khó khăn trong cả Java và .net, vì cơ chế sử dụng liên kết mật thiết với nhau ba khái niệm có phần liên quan nhưng cũng có phần trực giao: (1) Loại sự việc đã xảy ra sai (lỗi giới hạn mảng, thời gian chờ I / O, v.v.); (2) Kết quả là một số mã nhất định có hành động hay không; (3) Vấn đề cần được coi là "giải quyết" ở điểm nào. Hãy xem xét một quy trình được cho là thay đổi một đối tượng với dữ liệu được đọc từ IEnumerable. Điều gì sẽ xảy ra nếu một ngoại lệ xảy ra trong quá trình xử lý IEnumerable đó?
— supercat

3

Phương thức .Parameters.Add () trên SqlCommand trong V1 của khung công tác được thiết kế khủng khiếp - một trong những quá tải về cơ bản sẽ không hoạt động nếu bạn truyền vào một tham số có giá trị (int) bằng 0 - điều này dẫn đến việc chúng tạo ra phương thức .Parameters.AddWithValue () trên lớp SqlCommand.


Tôi đồng ý, nhưng tôi nghĩ ý bạn là phương thức SqlCommand.Parameters.Add ().
— Matt Peterson ngày

3
  1. Không có tập con nào của ICollection<T>và IList<T>; tối thiểu, một giao diện thu thập hiệp biến chỉ đọc IListSource<out T>(với một điều tra viên, bộ lập chỉ mục và Bộ đếm) sẽ cực kỳ hữu ích.
  2. .NET không hỗ trợ các đại biểu yếu . Các giải pháp thay thế tốt nhất là vụng về và các giải pháp giải quyết phía người nghe là không thể tin tưởng một phần (cần có ReflectionPermission).
  3. Hợp nhất giao diện chung bị cấm ngay cả khi nó có ý nghĩa và không gây ra vấn đề gì.
  4. Không giống như trong C ++, kiểu trả về hiệp phương sai không được phép trong .NET
  5. Không thể so sánh theo từng bit-so sánh hai kiểu giá trị để có giá trị bằng nhau. Trong cấu trúc dữ liệu " liên tục " chức năng , tôi đã viếtTransform(Sequence<T>, Func<T,T>) hàm cần xác định nhanh xem hàm trả về cùng một giá trị hay một giá trị khác. Nếu hàm không sửa đổi hầu hết / tất cả các đối số của nó, thì chuỗi đầu ra có thể chia sẻ một số / tất cả bộ nhớ từ chuỗi đầu vào. Nếu không có khả năng so sánh bitwise bất kỳ loại giá trị T nào, thì phải sử dụng phép so sánh chậm hơn nhiều, điều này làm ảnh hưởng rất nhiều đến hiệu suất.
  6. .NET dường như không thể hỗ trợ các giao diện đặc biệt (như những giao diện được cung cấp trong Go hoặc Rust) một cách hiệu quả. Các giao diện như vậy sẽ cho phép bạn truyền List<T>đến một giả định IListSource<U>(trong đó T: U) mặc dù lớp không thực thi giao diện đó một cách rõ ràng. Có ít nhất ba thư viện khác nhau (được viết độc lập) để cung cấp chức năng này (tất nhiên là có những hạn chế về hiệu suất - nếu có thể có một giải pháp hoàn hảo, sẽ không công bằng nếu gọi nó là một lỗ hổng trong .NET).
  7. Các vấn đề về hiệu suất khác: IEnumerator yêu cầu hai lệnh gọi giao diện mỗi lần lặp. Con trỏ phương thức thuần túy (đại biểu mở có kích thước IntPtr) hoặc đại biểu được định kiểu giá trị (IntPtr * 2) là không thể. Mảng có kích thước cố định (kiểu T tùy ý) không thể được nhúng vào bên trong các lớp. Không có WeakReference<T>(bạn có thể dễ dàng viết của riêng bạn, nhưng nó sẽ sử dụng phôi bên trong.)
  8. Thực tế là các loại đại biểu giống hệt nhau được coi là không tương thích (không có chuyển đổi ngầm định) đã gây phiền toái cho tôi trong một số trường hợp (ví dụ: Predicate<T>vs Func<T,bool>). Tôi thường ước chúng ta có thể nhập cấu trúc cho các giao diện và đại biểu, để đạt được khớp nối lỏng lẻo hơn giữa các thành phần, bởi vì trong .NET, không đủ để các lớp trong các DLL độc lập triển khai cùng một giao diện - chúng cũng phải chia sẻ tham chiếu chung đến một phần ba DLL xác định giao diện.
  9. DBNull.Valuetồn tại mặc dù nullsẽ phục vụ cùng một mục đích tốt như nhau.
  10. C # không có toán tử ?? =; bạn phải viết variable = variable ?? value. Thật vậy, có một vài chỗ trong C # thiếu đối xứng. Ví dụ, bạn có thể viết if (x) y(); else z();(không có dấu ngoặc nhọn), nhưng bạn không thể viết try y(); finally z();.
  11. Khi tạo một luồng, không thể khiến cho luồng con kế thừa các giá trị của luồng-cục bộ từ luồng cha. BCL không chỉ không hỗ trợ điều này mà bạn không thể tự thực hiện nó trừ khi bạn tạo tất cả các luồng theo cách thủ công; ngay cả khi có một sự kiện tạo luồng, .NET không thể cho bạn biết "cha mẹ" hoặc "con" của một luồng nhất định.
  12. Thực tế là có hai thuộc tính độ dài khác nhau cho các kiểu dữ liệu khác nhau, "Độ dài" và "Đếm", là một điều phiền toái nhỏ.
  13. Tôi có thể nói mãi về thiết kế tồi tệ của WPF ... và WCF (mặc dù khá hữu ích cho một số trường hợp) cũng đầy mụn cóc. Nói chung, sự cồng kềnh, không trực quan và tài liệu hạn chế của nhiều thư viện con mới hơn của BCL khiến tôi không muốn sử dụng chúng. Rất nhiều nội dung mới có thể đơn giản hơn, nhỏ hơn, dễ sử dụng và dễ hiểu hơn, được liên kết chặt chẽ hơn, được tài liệu hóa tốt hơn, có thể áp dụng cho nhiều trường hợp sử dụng hơn, nhanh hơn và / hoặc được đánh máy mạnh hơn.
  14. Tôi thường bị cắn bởi sự kết hợp không cần thiết giữa bộ nhận thuộc tính và bộ định tuyến: Trong một lớp dẫn xuất hoặc giao diện dẫn xuất, bạn không thể chỉ cần thêm một bộ định vị khi lớp cơ sở hoặc giao diện cơ sở chỉ có một bộ định tuyến; nếu bạn ghi đè một getter thì bạn không được phép xác định một setter; và bạn không thể định nghĩa setter là ảo nhưng getter là không ảo.

Tôi đồng ý với bạn về một tập hợp con của IList<T>, mặc dù tôi sẽ sử dụng IReadableByIndex<out T>và IAppendable<in T>. Tôi cũng đồng ý với nhiều thứ khác của bạn.
— supercat

Đó là một cái tên thực sự dài. Có lẽ chúng ta có thể thỏa hiệp IListReader<T>;) - Tôi sử dụng từ "nguồn" làm từ trái nghĩa của "chìm" (một giao diện chỉ ghi).
— Qwertie,

Có thể IListSource<in T>hoặc IReadableList<out T>. Có thể có giá trị khi có các loại giao diện cơ sở bao gồm các phương thức không tồn tại trong tất cả các dẫn xuất, mặc dù tôi nghĩ rằng việc có các giao diện hơi chuyên biệt là điều tốt. Ví dụ, người ta có thể có một IList<T>phương thức chứa các phương thức thay đổi kích thước có thể hoạt động hoặc có thể không hoạt động và một IResizableList<T>phương thức triển khai các phương thức giống nhau, nhưng đảm bảo chúng sẽ hoạt động. Cách tiếp cận như vậy có thể hữu ích trong trường hợp một trường có thể giữ tham chiếu duy nhất còn tồn tại đến một danh sách có thể thay đổi hoặc một tham chiếu được chia sẻ đến một danh sách bất biến.
— supercat

Trong trường hợp như vậy, mã muốn thay đổi nội dung của danh sách sẽ kiểm tra xem đó có phải là loại có thể thay đổi hay không và nếu không, hãy tạo một phiên bản có thể thay đổi mới chứa các mục giống như danh sách bất biến và sau đó bắt đầu sử dụng nó. Sẽ thật khó chịu nếu mã phải liên tục nhập trường mỗi khi nó muốn sử dụng một phương pháp đột biến trên đó.
— supercat

@supercat Điều đó thật khó chịu vì C # không cung cấp một cách thực sự dễ dàng để kiểm tra xem một giao diện đã được triển khai và sử dụng nó ngay lập tức hay chưa. MS nên thêm một tính năng ngôn ngữ để làm cho nó dễ dàng hơn. Kỹ thuật ưa thích của tôi sẽ là một biểu thức ràng buộc if (rl:(list as IResizableList<T>) != null) rl.Add(...);, nhưng có những đề xuất khác. Là tác giả của nhiều bộ sưu tập và bộ điều hợp bộ sưu tập khác nhau, điều khiến tôi khó chịu là viết rất nhiều phương pháp giả tạo ngoại lệ. Là một người hâm mộ loại an toàn, tôi không muốn được phép gọi các phương pháp bất hợp pháp. Một người hâm mộ IntelliSense, tôi không muốn thấy chúng được liệt kê.
— Qwertie

2

Một điều khiến tôi ngạc nhiên trong 1.x là khi sử dụng System.Xml.XmlValidatingReader, ValidationEventHandler's ValidationEventArgskhông làm lộ ra phần bên dưới XmlSchemaException(được đánh dấu bên trong) có tất cả các thông tin hữu ích như linenumbervà position. Thay vào đó, bạn dự kiến ​​sẽ phân tích cú pháp này ra khỏi thuộc tính Chuỗi tin nhắn hoặc sử dụng phản chiếu để tìm hiểu nó. Không tốt lắm khi bạn muốn trả lại một lỗi được làm sạch hơn cho người dùng cuối.


1

Không thích rằng bạn không thể sử dụng các giá trị của một enum này trong một enum khác, ví dụ:

    enum Colors { white, blue, green, red, black, yellow }

    enum SpecialColors { Colors.blue, Colors.red, Colors.Yellow } 

2
Tuy nhiên, điều này thậm chí không có ý nghĩa. typeof(Color)! = typeof(SpecialColors).
— Kirk Woll

10
Thật dễ dàng đủ để làm:enum SpecialColors { blue = Colors.blue, red = Colors.red, yellow = Colors.Yellow }
— Trystan Spangler

0

Biến được nhập ngầm được triển khai IMO kém. Tôi biết bạn thực sự chỉ nên sử dụng chúng khi làm việc với các biểu thức Linq, nhưng thật khó chịu khi bạn không thể khai báo chúng bên ngoài phạm vi cục bộ.

Từ MSDN:

  • var chỉ có thể được sử dụng khi một biến cục bộ được khai báo và khởi tạo trong cùng một câu lệnh; biến không thể được khởi tạo thành null, hoặc một nhóm phương thức hoặc một hàm ẩn danh.
  • var không thể được sử dụng trên các trường ở phạm vi lớp.
  • Các biến được khai báo bằng cách sử dụng var không thể được sử dụng trong biểu thức khởi tạo. Nói cách khác, biểu thức này là hợp pháp: int i = (i = 20); nhưng biểu thức này tạo ra lỗi thời gian biên dịch: var i = (i = 20);
  • Nhiều biến được định kiểu ngầm định không thể được khởi tạo trong cùng một câu lệnh.
  • Nếu một kiểu có tên var nằm trong phạm vi, thì từ khóa var sẽ phân giải thành tên kiểu đó và sẽ không được coi là một phần của khai báo biến cục bộ được nhập ngầm.

Lý do tôi nghĩ rằng đó là một triển khai kém là họ gọi nó là var, nhưng còn lâu mới trở thành một biến thể. Nó thực sự chỉ là cú pháp viết tắt để không phải nhập tên lớp đầy đủ (ngoại trừ khi được sử dụng với Linq)


Chắc chắn là loại anon (mới {...}), không được nhập ngầm (var)
— Marc Gravell

Chỉ cần đọc lại những gì tôi đã đăng và nó là sai. Ý tôi là đã nhập ngầm các biến tho
— lomaxx

1
Eric Lippert giải thích tại sao var không thể được sử dụng bên ngoài các phương thức, về cơ bản là vì nó tạo ra một hộp đen các khả năng. blogs.msdn.com/ericlippert/archive/2009/01/26/...
— Guvante

1
var không có ý định biến thể! nó chính xác dành cho tốc ký (đặc biệt là các loại anon). tận hưởng sự năng động khi nó đi cùng ...
— ShuggyCoUk 21/02/09
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 cookie và Chính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.