Đó có phải là thực hành tốt để kế thừa từ các loại chung chung?


35

Là nó tốt hơn để sử dụng List<string>trong các chú thích loại hoặc StringListnơiStringList

class StringList : List<String> { /* no further code!*/ }

Tôi gặp một vài trong số nàyIrony .


Nếu bạn không thừa kế từ các loại chung chung, thì tại sao chúng ở đó?
Mawg

7
@Mawg Chúng chỉ có thể có sẵn để khởi tạo.
Theodoros Chatzigiannakis 17/12/14

3
Đây là một trong những điều yêu thích ít nhất của tôi để xem trong mã
Kik

4
Kinh quá. Không, nghiêm túc. Sự khác biệt về ngữ nghĩa giữa StringListList<string>? Vẫn chưa có ai, nhưng bạn ("bạn" chung chung) đã quản lý để thêm một chi tiết khác vào cơ sở mã chỉ dành riêng cho dự án của bạn và không có nghĩa gì với bất kỳ ai khác cho đến khi họ đã nghiên cứu mã. Tại sao che dấu tên của một danh sách các chuỗi chỉ để tiếp tục gọi nó là một danh sách các chuỗi? Mặt khác, nếu bạn muốn làm BagOBagels : List<string>tôi nghĩ điều đó có ý nghĩa hơn. Bạn có thể lặp lại nó như là vô số, người tiêu dùng bên ngoài không quan tâm đến việc triển khai - lòng tốt tất cả '.
Craig

1
XAML có một vấn đề trong đó bạn không thể sử dụng thuốc generic và bạn cần tạo một loại như thế này để đưa vào danh sách
Daniel Little

Câu trả lời:


27

Có một lý do khác tại sao bạn có thể muốn thừa kế từ một loại chung.

Microsoft khuyên bạn nên tránh lồng các kiểu chung trong chữ ký phương thức .

Sau đó, đó là một ý tưởng tốt, để tạo các loại tên miền doanh nghiệp cho một số khái quát của bạn. Thay vì có một IEnumerable<IDictionary<string, MyClass>>, tạo một loại MyDictionary : IDictionary<string, MyClass>và sau đó thành viên của bạn trở thành một IEnumerable<MyDictionary>, dễ đọc hơn nhiều. Nó cũng cho phép bạn sử dụng MyDipedia ở một số nơi, làm tăng tính minh chứng cho mã của bạn.

Điều này có vẻ không phải là một lợi ích to lớn, nhưng trong mã kinh doanh trong thế giới thực, tôi đã thấy thuốc generic sẽ làm cho mái tóc của bạn đứng vững. Những điều như List<Tuple<string, Dictionary<int, List<Dictionary<string, List<double>>>>>. Ý nghĩa của cái chung đó là không có cách rõ ràng. Tuy nhiên, nếu nó được xây dựng với một loạt các loại được tạo rõ ràng, ý định của nhà phát triển ban đầu có thể sẽ rõ ràng hơn rất nhiều. Hơn nữa, nếu loại chung chung đó cần thay đổi vì một số lý do, việc tìm và thay thế tất cả các trường hợp của loại chung đó có thể là một nỗi đau thực sự, so với việc thay đổi nó trong lớp dẫn xuất.

Cuối cùng, có một lý do khác khiến ai đó có thể muốn tạo ra các loại của riêng họ bắt nguồn từ thuốc generic. Hãy xem xét các lớp sau:

public class MyClass
{
    public ICollection<MyItems> MyItems { get; private set; }
    // plumbing code here
}

public class MyOtherClass
{
    public ICollection<MyItems> MyItemCache { get; private set; }
    // plumbing code here
}

public class MyConsumerClass
{
    public MyConsumerClass(ICollection<MyItems> myItems)
    {
        // use the collection
    }
}

Bây giờ làm thế nào để chúng ta biết mà ICollection<MyItems>để vượt qua vào constructor cho MyConsumerClass? Nếu chúng ta tạo các lớp dẫn xuất class MyItems : ICollection<MyItems>class MyItemCache : ICollection<MyItems>, MyConsumerClass có thể cực kỳ cụ thể về bộ sưu tập nào trong hai bộ sưu tập mà nó thực sự muốn. Điều này sau đó hoạt động rất độc đáo với một bộ chứa IoC, có thể giải quyết hai bộ sưu tập rất dễ dàng. Nó cũng cho phép lập trình viên chính xác hơn - nếu có ý nghĩa MyConsumerClasskhi làm việc với bất kỳ ICollection nào, họ có thể lấy hàm tạo chung. Tuy nhiên, nếu không bao giờ có ý nghĩa kinh doanh khi sử dụng một trong hai loại dẫn xuất, nhà phát triển có thể giới hạn tham số hàm tạo đối với loại cụ thể.

Tóm lại, thường có giá trị để lấy các loại từ các loại chung - nó cho phép mã rõ ràng hơn khi thích hợp và giúp dễ đọc và duy trì.


12
Đó là một khuyến nghị hoàn toàn vô lý của Microsoft.
Thomas Eding

7
Tôi không nghĩ rằng nó vô lý cho các API công khai. Khái quát lồng nhau rất khó hiểu trong nháy mắt và một loại tên có ý nghĩa hơn thường có thể hỗ trợ khả năng đọc.
Stephen

4
Tôi không nghĩ nó thật lố bịch, và nó chắc chắn ổn đối với các API riêng ... nhưng đối với các API công khai thì tôi không nghĩ đó là một ý tưởng hay. Ngay cả Microsoft cũng khuyên bạn nên chấp nhận và trả lại các loại cơ bản nhất khi bạn viết API có nghĩa là cho tiêu dùng công cộng.
Paulizedoust

35

Về nguyên tắc, không có sự khác biệt giữa kế thừa từ các loại chung và kế thừa từ bất cứ thứ gì khác.

Tuy nhiên, tại sao bạn lại xuất phát từ bất kỳ lớp học nào và không thêm bất cứ điều gì nữa?


17
Tôi đồng ý. Không đóng góp bất cứ điều gì, tôi đã đọc "StringList" và tự nghĩ, "Nó thực sự làm gì?"
Neil

1
Tôi cũng đồng ý, trong ngôn ngữ có thể kế thừa bạn sử dụng từ khóa "extends", đây là một lời nhắc tốt rằng nếu bạn sẽ kế thừa một lớp thì bạn nên mở rộng nó với chức năng tiếp theo.
Elliot Blackburn

14
-1, vì không hiểu rằng đây chỉ là một cách để tạo ra sự trừu tượng bằng cách chọn một tên khác - tạo ra một sự trừu tượng có thể là một lý do rất tốt để không thêm bất cứ điều gì vào lớp này.
Doc Brown

1
Hãy xem xét câu trả lời của tôi, tôi đi vào một số lý do mà ai đó có thể quyết định làm điều này.
NPSF3000

1
"tại sao bạn lại xuất phát từ bất kỳ lớp nào và không thêm bất cứ điều gì nữa?" Tôi đã thực hiện nó để kiểm soát người dùng. Bạn có thể tạo một điều khiển người dùng chung nhưng bạn không thể sử dụng nó trong trình thiết kế biểu mẫu cửa sổ, do đó bạn phải kế thừa từ nó, chỉ định loại và sử dụng loại đó.
George T

13

Tôi không biết rõ về C #, nhưng tôi tin rằng đây là cách duy nhất để thực hiện một typedef, điều mà các lập trình viên C ++ thường sử dụng vì một vài lý do:

  • Nó giữ cho mã của bạn trông giống như một biển các dấu ngoặc góc.
  • Nó cho phép bạn trao đổi các container bên dưới khác nhau nếu nhu cầu của bạn thay đổi sau đó và làm rõ rằng bạn có quyền làm như vậy.
  • Nó cho phép bạn đặt một loại tên phù hợp hơn trong ngữ cảnh.

Về cơ bản, họ đã loại bỏ lợi thế cuối cùng bằng cách chọn những cái tên nhàm chán, chung chung, nhưng hai lý do khác vẫn đứng vững.


7
Ơ ... Chúng không hoàn toàn giống nhau. Trong C ++, bạn có một bí danh theo nghĩa đen. StringList một List<string>- họ có thể sử dụng thay thế cho nhau. Điều này rất quan trọng khi làm việc với các API khác (hoặc khi hiển thị API của bạn), vì mọi người khác trên thế giới đều sử dụng List<string>.
Telastyn

2
Tôi nhận ra chúng không hoàn toàn giống nhau. Chỉ cần gần như có sẵn. Chắc chắn có những hạn chế đối với phiên bản yếu hơn.
Karl Bielefeldt

24
Không, using StringList = List<string>;là tương đương C # gần nhất của một typedef. Phân lớp như thế này sẽ ngăn việc sử dụng bất kỳ hàm tạo nào có đối số.
Pete Kirkham

4
@PeteKirkham: xác định StringList bằng cách usinggiới hạn nó vào tệp mã nguồn nơi usingđược đặt. Từ quan điểm này, thừa kế gần hơn typedef. Và việc tạo một lớp con không ngăn người ta thêm tất cả các hàm tạo cần thiết (mặc dù nó có thể tẻ nhạt).
Doc Brown

2
@ Random832: hãy để tôi diễn đạt điều này một cách khác biệt: trong C ++, một typedef có thể được sử dụng để gán tên ở một nơi , để biến nó thành "nguồn duy nhất của sự thật". Trong C #, usingkhông thể được sử dụng cho mục đích này, nhưng kế thừa có thể. Điều này cho thấy lý do tại sao tôi không đồng ý với nhận xét được đánh giá cao của Pete Kirkham - Tôi không coi đó usinglà một thay thế thực sự cho C ++ typedef.
Doc Brown

9

Tôi có thể nghĩ về ít nhất một lý do thực tế.

Khai báo các loại không chung chung kế thừa từ các loại chung (thậm chí không thêm bất cứ thứ gì), cho phép các loại đó được tham chiếu từ những nơi mà nếu không chúng sẽ không có sẵn, hoặc có sẵn theo cách phức tạp hơn mức cần thiết.

Một trường hợp như vậy là khi thêm cài đặt thông qua trình chỉnh sửa Cài đặt trong Visual Studio, nơi chỉ có các loại không chung chung. Nếu bạn đến đó, bạn sẽ nhận thấy rằng tất cả System.Collectionscác lớp đều có sẵn, nhưng không có bộ sưu tập nào System.Collections.Genericxuất hiện dưới dạng áp dụng.

Một trường hợp khác là khi khởi tạo các loại thông qua XAML trong WPF. Không có hỗ trợ cho việc truyền các đối số kiểu trong cú pháp XML khi sử dụng lược đồ trước năm 2009. Điều này trở nên tồi tệ hơn bởi thực tế là lược đồ năm 2006 dường như là mặc định cho các dự án WPF mới.


1
Trong giao diện dịch vụ web WCF, bạn có thể sử dụng kỹ thuật này để cung cấp tên loại bộ sưu tập tìm kiếm tốt hơn (ví dụ: PersonCollection thay vì ArrayOfPerson hoặc bất cứ thứ gì)
Rico Suter

7

Tôi nghĩ rằng bạn cần phải nhìn vào một số lịch sử .

  • C # đã được sử dụng lâu hơn rất nhiều so với các loại chung cho.
  • Tôi hy vọng rằng phần mềm đã cho có StringList riêng tại một số điểm trong lịch sử.
  • Điều này có thể đã được thực hiện bằng cách gói một ArrayList.
  • Sau đó, một người nào đó tái cấu trúc để di chuyển cơ sở mã về phía trước.
  • Nhưng không muốn chạm vào 1001 tệp khác nhau, vì vậy hãy hoàn thành công việc.

Mặt khác, Theodoros Chatzigiannakis có câu trả lời tốt nhất, ví dụ như vẫn còn nhiều công cụ không hiểu các loại chung chung. Hoặc thậm chí có thể là một kết hợp của câu trả lời và lịch sử của mình.


3
"C # đã được sử dụng lâu hơn rất nhiều so với loại chung chung." Không thực sự, C # không có thuốc generic tồn tại trong khoảng 4 năm (tháng 1 năm 2002 - tháng 11 năm 2005), trong khi C # với thuốc generic hiện đã được 9 năm.
Svick


4

Trong một số trường hợp không thể sử dụng các loại chung chung, ví dụ bạn không thể tham chiếu một loại chung trong XAML. Trong những trường hợp này, bạn có thể tạo một loại không chung chung kế thừa từ loại chung mà bạn muốn sử dụng ban đầu.

Nếu có thể, tôi sẽ tránh nó.

Không giống như một typedef, bạn tạo một loại mới. Nếu bạn sử dụng StringList làm tham số đầu vào cho một phương thức, người gọi của bạn không thể vượt qua a List<string>.

Ngoài ra, bạn sẽ cần sao chép rõ ràng tất cả các hàm tạo mà bạn muốn sử dụng, trong ví dụ StringList mà bạn không thể nói new StringList(new[]{"a", "b", "c"}), mặc dù List<T>định nghĩa một hàm tạo như vậy.

Chỉnh sửa:

Câu trả lời được chấp nhận tập trung vào chuyên nghiệp khi sử dụng các loại dẫn xuất bên trong mô hình miền của bạn. Tôi đồng ý rằng chúng có khả năng có thể hữu ích trong trường hợp này, tuy nhiên, cá nhân tôi sẽ tránh chúng ngay cả ở đó.

Nhưng theo tôi, bạn không bao giờ nên sử dụng chúng trong API công khai vì bạn sẽ làm cho cuộc sống của người gọi của bạn khó khăn hơn hoặc lãng phí hiệu suất (nói chung tôi cũng không thực sự thích chuyển đổi ngầm).


3
Bạn nói, " bạn không thể tham chiếu một loại chung trong XAML ", nhưng tôi không chắc bạn đang đề cập đến cái gì. Xem Generics trong XAML
pswg 17/12/14

1
Nó có nghĩa là một ví dụ. Có, có thể với Lược đồ XAML 2009, nhưng AFAIK không phải với Lược đồ 2006, vẫn là mặc định của VS.
Lukas Rieger

1
Đoạn thứ ba của bạn chỉ đúng một nửa, bạn thực sự có thể cho phép điều này với các trình chuyển đổi ngầm;)
Knerd 22/12/14

1
@Knerd, đúng, nhưng sau đó bạn 'ngầm' tạo một đối tượng mới. Trừ khi bạn làm nhiều việc hơn, điều này cũng sẽ tạo ra một bản sao của dữ liệu. Có lẽ không thành vấn đề với các danh sách nhỏ, nhưng hãy vui vẻ, nếu ai đó vượt qua một danh sách có vài nghìn yếu tố =)
Lukas Rieger

@LukasRieger bạn là đúng: PI chỉ muốn thời điểm đó ra: P
Knerd

2

Để biết giá trị của nó, đây là một ví dụ về cách kế thừa từ một lớp chung được sử dụng trong trình biên dịch Roslyn của Microsoft và thậm chí không thay đổi tên của lớp. (Tôi đã rất bối rối vì điều này đến nỗi tôi đã kết thúc ở đây để tìm kiếm xem điều này có thực sự khả thi không.)

Trong dự án CodeAnalysis, bạn có thể tìm thấy định nghĩa này:

/// <summary>
/// Common base class for C# and VB PE module builder.
/// </summary>
internal abstract class PEModuleBuilder<TCompilation, TSourceModuleSymbol, TAssemblySymbol, TTypeSymbol, TNamedTypeSymbol, TMethodSymbol, TSyntaxNode, TEmbeddedTypesManager, TModuleCompilationState> : CommonPEModuleBuilder, ITokenDeferral
    where TCompilation : Compilation
    where TSourceModuleSymbol : class, IModuleSymbol
    where TAssemblySymbol : class, IAssemblySymbol
    where TTypeSymbol : class
    where TNamedTypeSymbol : class, TTypeSymbol, Cci.INamespaceTypeDefinition
    where TMethodSymbol : class, Cci.IMethodDefinition
    where TSyntaxNode : SyntaxNode
    where TEmbeddedTypesManager : CommonEmbeddedTypesManager
    where TModuleCompilationState : ModuleCompilationState<TNamedTypeSymbol, TMethodSymbol>
{
  ...
}

Sau đó, trong dự án CSharpCodeanalysis có định nghĩa này:

internal abstract class PEModuleBuilder : PEModuleBuilder<CSharpCompilation, SourceModuleSymbol, AssemblySymbol, TypeSymbol, NamedTypeSymbol, MethodSymbol, SyntaxNode, NoPia.EmbeddedTypesManager, ModuleCompilationState>
{
  ...
}

Lớp PEModuleBuilder không chung này được sử dụng rộng rãi trong dự án CSharpCodeanalysis và một số lớp trong dự án đó kế thừa từ nó, trực tiếp hoặc gián tiếp.

Và sau đó trong dự án BasicCodeanalysis có định nghĩa này:

Partial Friend MustInherit Class PEModuleBuilder
    Inherits PEModuleBuilder(Of VisualBasicCompilation, SourceModuleSymbol, AssemblySymbol, TypeSymbol, NamedTypeSymbol, MethodSymbol, SyntaxNode, NoPia.EmbeddedTypesManager, ModuleCompilationState)

Vì chúng ta có thể (hy vọng) cho rằng Roslyn được viết bởi những người có kiến ​​thức sâu rộng về C # và cách sử dụng nó nên tôi nghĩ rằng đây là một khuyến nghị về kỹ thuật.


Vâng. Và chúng cũng có thể được sử dụng để tinh chỉnh mệnh đề where trực tiếp khi khai báo.
Sentinel

1

Có ba lý do có thể khiến ai đó cố gắng làm điều đó ra khỏi đầu tôi:

1) Để đơn giản hóa các khai báo:

Chắc chắn StringListListStringcó cùng độ dài nhưng hãy tưởng tượng thay vì bạn đang làm việc với một ví dụ chung UserCollectionthực sự Dictionary<Tuple<string,Type>, IUserData<Dictionary,MySerializer>>hoặc một số ví dụ chung lớn khác.

2) Để giúp AOT:

Đôi khi bạn cần sử dụng trình biên dịch Ahead Of Time không chỉ là Biên dịch theo thời gian - ví dụ: phát triển cho iOS. Đôi khi trình biên dịch AOT gặp khó khăn trong việc tìm ra tất cả các loại chung trước thời hạn và đây có thể là một nỗ lực để cung cấp cho nó một gợi ý.

3) Để thêm chức năng hoặc loại bỏ sự phụ thuộc:

Có thể họ có chức năng cụ thể mà họ muốn thực hiện cho StringList(có thể được ẩn trong một phương pháp khuyến nông, chưa thêm bài viết nào, hoặc lịch sử) rằng họ hoặc là không thể thêm vào List<string>mà không kế thừa từ nó, hoặc là họ không muốn làm ô nhiễm List<string>với .

Ngoài ra, họ có thể muốn thay đổi việc triển khai StringListtheo dõi, vì vậy hiện tại họ chỉ sử dụng lớp làm điểm đánh dấu (thông thường bạn sẽ tạo / sử dụng giao diện cho ví dụ này IList).


Tôi cũng lưu ý rằng bạn đã tìm thấy mã này trong Irony, một trình phân tích cú pháp ngôn ngữ - một loại ứng dụng khá bất thường. Có thể có một số lý do cụ thể khác mà điều này đã được sử dụng.

Những ví dụ này chứng minh rằng có thể có những lý do chính đáng để viết một tuyên bố như vậy - ngay cả khi nó không quá phổ biến. Như với bất cứ điều gì xem xét một số tùy chọn và chọn đó là tốt nhất cho bạn tại thời điểm đó.


Nếu bạn xem mã nguồn , tùy chọn 3 xuất hiện là lý do - họ sử dụng kỹ thuật này để giúp thêm chức năng / các bộ sưu tập chuyên biệt:

public class StringList : List<string> {
    public StringList() { }
    public StringList(params string[] args) {
      AddRange(args);
    }
    public override string ToString() {
      return ToString(" ");
    }
    public string ToString(string separator) {
      return Strings.JoinStrings(separator, this);
    }
    //Used in sorting suffixes and prefixes; longer strings must come first in sort order
    public static int LongerFirst(string x, string y) {
      try {//in case any of them is null
        if (x.Length > y.Length) return -1;
      } catch { }
      if (x == y) return 0;
      return 1; 
    }

1
Đôi khi, nhiệm vụ đơn giản hóa các khai báo (hoặc đơn giản hóa bất cứ điều gì) có thể dẫn đến sự phức tạp gia tăng, thay vì giảm độ phức tạp. Mọi người biết ngôn ngữ vừa phải đều biết a List<T>là gì , nhưng họ phải kiểm tra StringListtheo ngữ cảnh để thực sự hiểu là gì . Trong thực tế, nó chỉ là List<string>, đi bằng một tên khác. Thực sự không có bất kỳ lợi thế ngữ nghĩa nào để làm điều này trong trường hợp đơn giản như vậy. Bạn thực sự chỉ cần thay thế một loại nổi tiếng bằng cùng một loại bằng một tên khác.
Craig

@Craig Tôi đồng ý, như được lưu ý bởi lời giải thích của tôi về usecase (khai báo dài hơn) và các lý do có thể khác.
NPSF3000

-1

Tôi muốn nói rằng nó không phải là thực hành tốt.

List<string>truyền đạt "một danh sách chung của các chuỗi". Rõ ràng List<string> phương pháp mở rộng áp dụng. Ít phức tạp hơn là cần thiết để hiểu; Danh sách duy nhất là cần thiết.

StringListtruyền đạt "nhu cầu về một lớp học riêng biệt". Có thể List<string> áp dụng các phương pháp mở rộng (kiểm tra thực hiện loại thực tế cho việc này). Sự phức tạp hơn là cần thiết để hiểu mã; Danh sách và kiến ​​thức thực hiện lớp dẫn xuất được yêu cầu.


4
Phương pháp mở rộng áp dụng anyway.
Theodoros Chatzigiannakis 17/12/14

2
Tất nhiên rồi. Nhưng bạn không biết rằng cho đến khi bạn kiểm tra StringList và xem nó xuất phát từ đâu List<string>.
Ruudjah

@TheodorosChatzigiannakis Tôi tự hỏi liệu Ruudjah có thể nói rõ hơn về ý nghĩa của nó bằng "phương pháp mở rộng không áp dụng." Các phương thức mở rộng là một khả năng với bất kỳ lớp nào . Chắc chắn, thư viện .NET framework có rất nhiều phương thức mở rộng được gọi một cách vô cớ là LINQ áp dụng cho các lớp bộ sưu tập chung, nhưng điều đó không có nghĩa là các phương thức mở rộng không áp dụng cho bất kỳ lớp nào khác? Có lẽ Ruudjah đang tạo ra một điểm không rõ ràng ngay từ cái nhìn đầu tiên? ;-)
Craig

"phương pháp mở rộng không áp dụng." Bạn đọc cái này ở đâu? Tôi đang nói "phương pháp mở rộng có thể được áp dụng.
Ruudjah

1
Mặc dù vậy, việc triển khai kiểu sẽ không cho bạn biết liệu các phương thức mở rộng có áp dụng hay không? Chỉ cần một thực tế rằng đó là một phần mở rộng phương tiện lớp phương pháp làm áp dụng. Bất kỳ người tiêu dùng nào của bạn có thể tạo các phương thức mở rộng hoàn toàn độc lập với mã của bạn. Tôi đoán đó là những gì tôi nhận được hoặc hỏi. Bạn chỉ đang nói về LINQ? LINQ được thực hiện bằng các phương pháp mở rộng. Nhưng sự vắng mặt của các phương thức mở rộng dành riêng cho LINQ cho một loại cụ thể không có nghĩa là các phương thức mở rộng cho loại đó không tồn tại hoặc sẽ không tồn tại. Chúng có thể được tạo ra bất cứ lúc nào bởi bất cứ ai.
Craig
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.