Thống nhất là gì?


163

Tôi biết rằng Java thực hiện đa hình tham số (Generics) với việc xóa. Tôi hiểu tẩy là gì.

Tôi biết rằng C # thực hiện đa hình tham số với sự thống nhất. Tôi biết điều đó có thể khiến bạn viết

public void dosomething(List<String> input) {}
public void dosomething(List<Int> input) {}

hoặc bạn có thể biết trong thời gian chạy tham số loại của một số loại tham số là gì, nhưng tôi không hiểu nó gì .

  • Một loại thống nhất là gì?
  • Giá trị hợp nhất là gì?
  • Điều gì xảy ra khi một loại / giá trị được thống nhất?

Đó không phải là một câu trả lời, nhưng có thể giúp một cách nào đó: beust.com/weblog/2011/07/29/erasure-vs-reification
heringer

@heringer dường như trả lời câu hỏi "cái gì đang xóa" khá tốt, và dường như về cơ bản trả lời "sự thống nhất" với "không xóa" - một chủ đề phổ biến tôi tìm thấy khi ban đầu tìm kiếm câu trả lời trước khi đăng ở đây.
Martijn

5
... và tôi đã nghĩ rằng ific ication là quá trình chuyển đổi một switchcấu trúc trở lại thành if/ else, khi trước đó nó đã được chuyển đổi từ if/ elsethành switch...
Chấn thương kỹ thuật số

8
Res , reis là tiếng Latin có nghĩa điều , vì vậy reification là nghĩa đen thingification . Tôi không có gì hữu ích để đóng góp cho đến khi sử dụng thuật ngữ của C #, nhưng thực tế là chính họ đã sử dụng nó khiến tôi mỉm cười.
KRyan

Câu trả lời:


209

Sự thống nhất là quá trình lấy một thứ trừu tượng và tạo ra một thứ cụ thể.

Thuật ngữ hợp nhất hóa trong tổng quát C # đề cập đến quá trình định nghĩa kiểu chung và một hoặc nhiều đối số kiểu chung (điều trừu tượng) được kết hợp để tạo ra một kiểu chung mới (điều cụ thể).

Để cụm từ theo cách khác, nó là quá trình lấy định nghĩa về List<T>intvà sản xuất một bê tông List<int>loại.

Để hiểu rõ hơn, hãy so sánh các cách tiếp cận sau:

  • Trong tổng quát Java, một định nghĩa kiểu chung được chuyển đổi thành cơ bản một kiểu chung cụ thể được chia sẻ trên tất cả các kết hợp đối số kiểu được phép. Do đó, nhiều loại (mức mã nguồn) được ánh xạ thành một loại (mức nhị phân) - nhưng kết quả là, thông tin về các đối số loại của một thể hiện sẽ bị loại bỏ trong trường hợp đó (loại xóa) .

    1. Là một tác dụng phụ của kỹ thuật triển khai này, các đối số loại chung duy nhất được phép vốn là những loại có thể chia sẻ mã nhị phân của loại cụ thể của chúng; có nghĩa là những loại có vị trí lưu trữ có đại diện hoán đổi cho nhau; có nghĩa là các loại tài liệu tham khảo. Sử dụng các loại giá trị làm đối số loại chung đòi hỏi phải có chúng (đặt chúng trong một trình bao bọc kiểu tham chiếu đơn giản).
    2. Không có mã nào được sao chép để thực hiện generic theo cách này.
    3. Nhập thông tin có thể có sẵn trong thời gian chạy (sử dụng sự phản chiếu) sẽ bị mất. Đến lượt mình, điều này có nghĩa là việc chuyên môn hóa một loại chung (khả năng sử dụng mã nguồn chuyên biệt cho bất kỳ kết hợp đối số chung cụ thể nào) là rất hạn chế.
    4. Cơ chế này không yêu cầu hỗ trợ từ môi trường thời gian chạy.
    5. Có một vài cách giải quyết để giữ lại thông tin kiểu mà chương trình Java hoặc ngôn ngữ dựa trên JVM có thể sử dụng.
  • Trong C # generic, định nghĩa loại chung được duy trì trong bộ nhớ khi chạy. Bất cứ khi nào một loại cụ thể mới được yêu cầu, môi trường thời gian chạy kết hợp định nghĩa loại chung và các đối số loại và tạo ra loại mới (thống nhất). Vì vậy, chúng tôi nhận được một loại mới cho mỗi kết hợp của các đối số loại, trong thời gian chạy .

    1. Kỹ thuật triển khai này cho phép bất kỳ loại kết hợp đối số loại nào được khởi tạo. Sử dụng các loại giá trị làm đối số loại chung không gây ra quyền anh, vì các loại này có triển khai riêng. (Tất nhiên, quyền anh vẫn tồn tại trong C # - nhưng nó xảy ra trong các tình huống khác, không phải trong trường hợp này.)
    2. Sao chép mã có thể là một vấn đề - nhưng trên thực tế thì không, vì các triển khai đủ thông minh ( bao gồm Microsoft .NETMono ) có thể chia sẻ mã cho một số cảnh báo.
    3. Thông tin loại được duy trì, cho phép chuyên môn hóa đến một mức độ, bằng cách kiểm tra các đối số loại bằng cách sử dụng sự phản chiếu. Tuy nhiên, mức độ chuyên môn hóa bị hạn chế, do thực tế là một định nghĩa loại chung được biên dịch trước khi bất kỳ sự thống nhất nào xảy ra (điều này được thực hiện bằng cách biên dịch định nghĩa chống lại các ràng buộc trên các tham số loại - do đó, trình biên dịch phải có khả năng "Hiểu" định nghĩa ngay cả khi không có đối số loại cụ thể ).
    4. Kỹ thuật triển khai này phụ thuộc rất nhiều vào hỗ trợ thời gian chạy và biên dịch JIT (đó là lý do tại sao bạn thường nghe rằng tướng C # có một số hạn chế trên các nền tảng như iOS , nơi việc tạo mã động bị hạn chế).
    5. Trong ngữ cảnh của C # generic, việc thống nhất được thực hiện cho bạn bởi môi trường thời gian chạy. Tuy nhiên, nếu bạn muốn hiểu một cách trực quan hơn sự khác biệt giữa định nghĩa loại chung và loại chung chung cụ thể, bạn luôn có thể tự mình thực hiện việc thống nhất bằng cách sử dụng System.Typelớp (ngay cả khi kết hợp đối số loại chung cụ thể mà bạn đang tạo ra ' t xuất hiện trong mã nguồn của bạn trực tiếp).
  • Trong các mẫu C ++, định nghĩa mẫu được duy trì trong bộ nhớ tại thời gian biên dịch. Bất cứ khi nào một khởi tạo mới của một kiểu mẫu được yêu cầu trong mã nguồn, trình biên dịch sẽ kết hợp định nghĩa mẫu và các đối số khuôn mẫu và tạo ra kiểu mới. Vì vậy, chúng tôi nhận được một loại duy nhất cho mỗi kết hợp của các đối số mẫu, tại thời gian biên dịch .

    1. Kỹ thuật triển khai này cho phép bất kỳ loại kết hợp đối số loại nào được khởi tạo.
    2. Điều này được biết là sao chép mã nhị phân nhưng một chuỗi công cụ đủ thông minh vẫn có thể phát hiện ra điều này và chia sẻ mã cho một số cảnh báo.
    3. Bản thân định nghĩa mẫu không được "biên dịch" - chỉ các phần khởi tạo cụ thể của nó mới thực sự được biên dịch . Điều này đặt ít ràng buộc hơn trên trình biên dịch và cho phép mức độ chuyên môn hóa mẫu lớn hơn .
    4. Vì việc khởi tạo mẫu được thực hiện tại thời gian biên dịch, nên không cần hỗ trợ thời gian chạy ở đây.
    5. Quá trình này gần đây được gọi là đơn hình hóa , đặc biệt là trong cộng đồng Rust. Từ này được sử dụng trái ngược với đa hình tham số , đó là tên của khái niệm mà thuốc generic xuất phát.

7
So sánh tuyệt vời với các mẫu C ++ ... dường như chúng nằm ở đâu đó giữa các tổng quát của C # và Java. Bạn có mã và cấu trúc khác nhau để xử lý các loại chung cụ thể khác nhau như trong C #, nhưng tất cả đều được thực hiện trong thời gian biên dịch như trong Java.
Luaan

3
Ngoài ra, trong C ++, điều này cho phép giới thiệu chuyên môn hóa mẫu, trong đó mỗi loại (hoặc chỉ một số) cụ thể có thể có các triển khai khác nhau. Rõ ràng là không thể có trong Java, nhưng cũng không có trong C #.
quetzalcoatl

@quetzalcoatl mặc dù một lý do để sử dụng đó là để giảm số lượng mã được sản xuất với các loại con trỏ và C # thực hiện một cái gì đó có thể so sánh với các loại tham chiếu đằng sau hậu trường. Tuy nhiên, đó chỉ là một lý do để sử dụng điều đó, và chắc chắn sẽ có những lúc chuyên môn hóa mẫu sẽ tốt đẹp.
Jon Hanna

Đối với Java, bạn có thể muốn thêm rằng trong khi thông tin loại bị xóa, các phôi được thêm vào bởi trình biên dịch, làm cho mã byte không thể phân biệt được với mã byte tiền chung.
Rusty Core

27

Reization có nghĩa là nói chung (bên ngoài khoa học máy tính) "để làm cho một cái gì đó thực sự".

Trong lập trình, cái gì đó đang reified nếu chúng tôi có thể truy cập thông tin về nó bằng ngôn ngữ riêng của mình.

Đối với hai ví dụ hoàn toàn không liên quan đến khái niệm về một thứ C # nào đó và không được thống nhất, hãy sử dụng các phương thức và truy cập bộ nhớ.

Các ngôn ngữ OO thường có các phương thức , (và nhiều ngôn ngữ không có các chức năng tương tự nhau mặc dù không bị ràng buộc với một lớp). Như vậy, bạn có thể định nghĩa một phương thức trong một ngôn ngữ như vậy, gọi nó, có thể ghi đè lên nó, v.v. Không phải tất cả các ngôn ngữ như vậy cho phép bạn thực sự đối phó với chính phương thức đó là dữ liệu cho một chương trình. C # (và thực sự, .NET chứ không phải C #) không cho phép bạn sử dụng các MethodInfođối tượng đại diện cho các phương thức, vì vậy trong các phương thức C # được thống nhất. Các phương thức trong C # là "đối tượng hạng nhất".

Tất cả các ngôn ngữ thực tế có một số phương tiện để truy cập vào bộ nhớ của máy tính. Trong một ngôn ngữ cấp thấp như C, chúng ta có thể xử lý trực tiếp ánh xạ giữa các địa chỉ số được sử dụng bởi máy tính, vì vậy lượt thích int* ptr = (int*) 0xA000000; *ptr = 42;là hợp lý (miễn là chúng ta có lý do chính đáng để nghi ngờ rằng việc truy cập địa chỉ bộ nhớ 0xA000000theo cách này sẽ thắng ' t thổi một cái gì đó lên). Trong C #, điều này không hợp lý (chúng ta có thể ép buộc nó bằng .NET, nhưng với việc quản lý bộ nhớ .NET, mọi thứ xung quanh sẽ không hữu dụng lắm). C # không có địa chỉ bộ nhớ hợp nhất.

Vì vậy, vì từ chối có nghĩa là "thực tế", "loại thống nhất" là loại mà chúng ta có thể "nói về" trong ngôn ngữ được đề cập.

Trong thuốc generic có nghĩa là hai điều.

Một trong số đó List<string>là một loại giống như stringhoặc intlà. Chúng ta có thể so sánh loại đó, lấy tên của nó và hỏi về nó:

Console.WriteLine(typeof(List<string>).FullName); // System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
Console.WriteLine(typeof(List<string>) == (42).GetType()); // False
Console.WriteLine(typeof(List<string>) == Enumerable.Range(0, 1).Select(i => i.ToString()).ToList().GetType()); // True
Console.WriteLine(typeof(List<string>).GenericTypeArguments[0] == typeof(string)); // True

Hậu quả của điều này là chúng ta có thể "nói về" các kiểu tham số của phương thức chung (hoặc phương thức của lớp chung) trong chính phương thức:

public static void DescribeType<T>(T element)
{
  Console.WriteLine(typeof(T).FullName);
}
public static void Main()
{
  DescribeType(42);               // System.Int32
  DescribeType(42L);              // System.Int64
  DescribeType(DateTime.UtcNow);  // System.DateTime
}

Theo quy định, làm điều này quá nhiều là "có mùi", nhưng nó có nhiều trường hợp hữu ích. Ví dụ: nhìn vào:

public static TSource Min<TSource>(this IEnumerable<TSource> source)
{
  if (source == null) throw Error.ArgumentNull("source");
  Comparer<TSource> comparer = Comparer<TSource>.Default;
  TSource value = default(TSource);
  if (value == null)
  {
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
      do
      {
        if (!e.MoveNext()) return value;
        value = e.Current;
      } while (value == null);
      while (e.MoveNext())
      {
        TSource x = e.Current;
        if (x != null && comparer.Compare(x, value) < 0) value = x;
      }
    }
  }
  else
  {
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
      if (!e.MoveNext()) throw Error.NoElements();
      value = e.Current;
      while (e.MoveNext())
      {
        TSource x = e.Current;
        if (comparer.Compare(x, value) < 0) value = x;
      }
    }
  }
  return value;
}

Điều này không có nhiều so sánh giữa loại TSourcevà loại khác nhau cho các hành vi khác nhau (nói chung là một dấu hiệu bạn không nên sử dụng chung chung) nhưng nó phân chia giữa một đường dẫn mã cho các loại có thể null(nên trả về nullnếu không tìm thấy phần tử nào và không được so sánh để tìm mức tối thiểu nếu một trong các phần tử được so sánh là null) và đường dẫn mã cho các loại không thể null(nên ném nếu không tìm thấy phần tử nào và không phải lo lắng về khả năng của nullcác phần tử ).

TSourcelà "thực" trong phương thức, nên việc so sánh này có thể được thực hiện trong thời gian chạy hoặc thời gian jits (nói chung là thời gian jits, chắc chắn trường hợp trên sẽ làm như vậy tại thời điểm jits và không tạo mã máy cho đường dẫn không được thực hiện) và chúng tôi có một phiên bản "thực" riêng của phương pháp cho từng trường hợp. (Mặc dù là một tối ưu hóa, mã máy được chia sẻ cho các phương thức khác nhau cho các tham số loại tham chiếu khác nhau, bởi vì nó có thể không ảnh hưởng đến điều này và do đó chúng tôi có thể giảm số lượng mã máy bị giật).

(Đó là không phổ biến để nói về reification các loại chung trong C #, trừ khi bạn cũng đối phó với Java, bởi vì trong C # chúng ta chỉ mất reification này cho các cấp; tất cả các loại đang reified Trong Java, các loại phi generic được gọi là. Reified vì đó là một sự phân biệt giữa chúng và các loại chung chung).


Bạn không nghĩ rằng có thể làm những gì Minở trên hữu ích? Rất khó để thực hiện hành vi tài liệu của nó bằng cách khác.
Jon Hanna

Tôi coi lỗi là hành vi (chưa) được ghi lại và hàm ý rằng hành vi đó là hữu ích (như một bên, hành vi Enumerable.Min<TSource>khác ở chỗ nó không ném các loại không tham chiếu vào bộ sưu tập trống, nhưng trả về mặc định (TSource) và chỉ được ghi lại dưới dạng "Trả về giá trị tối thiểu theo trình tự chung." Tôi cho rằng cả hai nên ném vào một bộ sưu tập trống hoặc phần tử "không" nên được đưa vào làm đường cơ sở và bộ so sánh / chức năng so sánh phải luôn luôn được thông qua)
Martijn

1
Điều đó sẽ ít hữu ích hơn nhiều so với Min hiện tại, phù hợp với hành vi db phổ biến trên các loại nullable mà không cố gắng không thể trên các loại không nullable. (Ý tưởng cơ bản không phải là không thể, nhưng không hữu ích trừ khi có một giá trị bạn có thể biết sẽ không bao giờ có trong nguồn).
Jon Hanna

1
Thingification sẽ là một tên tốt hơn cho điều này. :)
tchrist

@tchrist một điều có thể không thật.
Jon Hanna

15

Như duffymo đã lưu ý , "sự thống nhất" không phải là điểm khác biệt chính.

Trong Java, về cơ bản, generic đã có để cải thiện hỗ trợ thời gian biên dịch - nó cho phép bạn sử dụng các bộ sưu tập được gõ mạnh, ví dụ như các bộ sưu tập trong mã của bạn và có loại an toàn được xử lý cho bạn. Tuy nhiên, điều này chỉ tồn tại vào thời gian biên dịch - mã byte được biên dịch không còn có bất kỳ khái niệm nào về khái quát; tất cả các loại chung được chuyển thành loại "cụ thể" (sử dụng objectnếu loại chung không bị ràng buộc), thêm chuyển đổi loại và kiểm tra loại nếu cần.

Trong .NET, generic là một tính năng không thể thiếu của CLR. Khi bạn biên dịch một kiểu chung, nó sẽ nằm chung trong IL được tạo. Nó không chỉ chuyển đổi thành mã không chung chung như trong Java.

Điều này có một số tác động đến cách thuốc generic hoạt động trong thực tế. Ví dụ:

  • Java SomeType<?>phải cho phép bạn vượt qua bất kỳ triển khai cụ thể nào của một loại chung chung nhất định. C # không thể làm điều này - mỗi cụ thể ( reified ) kiểu chung chung là kiểu riêng của mình.
  • Các kiểu chung không bị ràng buộc trong Java có nghĩa là giá trị của chúng được lưu trữ dưới dạng object. Điều này có thể có tác động hiệu suất khi sử dụng các loại giá trị trong các tổng quát như vậy. Trong C #, khi bạn sử dụng loại giá trị trong loại chung, nó sẽ giữ nguyên loại giá trị.

Để đưa ra một mẫu, giả sử bạn có một Listloại chung với một đối số chung. Trong Java, List<String>List<Int>cuối cùng sẽ là cùng loại chính xác trong thời gian chạy - các loại chung chỉ thực sự tồn tại cho mã thời gian biên dịch. Tất cả các cuộc gọi đến ví dụ GetValuesẽ được chuyển đổi (String)GetValue(Int)GetValuetương ứng.

Trong C #, List<string>List<int>là hai loại khác nhau. Chúng không thể thay thế cho nhau và loại an toàn của chúng cũng được thực thi trong thời gian chạy. Bất kể bạn làm gì, new List<int>().Add("SomeString")sẽ không bao giờ hoạt động - bộ lưu trữ bên dưới thực sựList<int> là một mảng số nguyên, trong khi ở Java, nó nhất thiết phải là một mảng. Trong C #, không có diễn viên tham gia, không có quyền anh, v.v.object

Điều này cũng sẽ làm rõ lý do tại sao C # không thể làm điều tương tự như Java với SomeType<?>. Trong Java, tất cả các loại chung "xuất phát từ" SomeType<?>cuối cùng là cùng một loại. Trong C #, tất cả các SomeType<T>s cụ thể khác nhau là loại riêng của chúng. Loại bỏ kiểm tra thời gian biên dịch, có thể vượt qua SomeType<Int>thay vì SomeType<String>(và thực sự, tất cả điều đó SomeType<?>có nghĩa là "bỏ qua kiểm tra thời gian biên dịch cho loại chung chung đã cho"). Trong C #, điều đó là không thể, ngay cả đối với các loại dẫn xuất (nghĩa là bạn không thể làm được List<object> list = (List<object>)new List<string>();ngay cả khi stringcó nguồn gốc từ object).

Cả hai triển khai đều có ưu và nhược điểm của họ. Đã có một vài lần tôi rất thích có thể chỉ cho phép SomeType<?>làm đối số trong C # - nhưng đơn giản là nó không có ý nghĩa như cách hoạt động của thuốc generic C #.


2
Chà, bạn có thể sử dụng các loại List<>, Dictionary<,>v.v. trong C #, nhưng khoảng cách giữa đó và một danh sách cụ thể hoặc từ điển cụ thể cần khá nhiều sự phản ánh để làm cầu nối. Phương sai trên các giao diện giúp ích trong một số trường hợp mà trước đây chúng ta có thể muốn thu hẹp khoảng cách đó một cách dễ dàng, nhưng không phải tất cả.
Jon Hanna

2
@JonHanna Bạn có thể sử dụng List<>để khởi tạo một loại chung cụ thể mới - nhưng nó vẫn có nghĩa là tạo loại cụ thể mà bạn muốn. Nhưng bạn không thể sử dụng List<>như một đối số, ví dụ. Nhưng có, ít nhất điều này cho phép bạn thu hẹp khoảng cách bằng cách sử dụng sự phản chiếu.
Luaan

.NET Framework có ba ràng buộc chung được mã hóa cứng không phải là loại vị trí lưu trữ; tất cả các ràng buộc khác phải là loại vị trí lưu trữ. Hơn nữa, lần duy nhất một loại chung Tcó thể đáp ứng ràng buộc kiểu lưu trữ Ulà khi TUcùng loại hoặc Ulà loại có thể chứa tham chiếu đến một thể hiện của T. Không thể có vị trí lưu trữ của loại một cách có ý nghĩa SomeType<?>nhưng về mặt lý thuyết có thể có một ràng buộc chung của loại đó.
supercat

1
Không đúng khi mã byte Java được biên dịch không có khái niệm về khái quát. Nó chỉ là lớp trường hợp không có khái niệm về Generics. Đây là một sự khác biệt quan trọng; Trước đây tôi đã viết về điều này tại lập trình viên.stackexchange.com/questions/280169/ , nếu bạn quan tâm.
ruakh 8/8/2015

2

Reization là một khái niệm mô hình hướng đối tượng.

Reify là một động từ có nghĩa là "làm cho một cái gì đó trừu tượng thực sự" .

Khi bạn thực hiện lập trình hướng đối tượng, việc mô hình hóa các đối tượng trong thế giới thực là các thành phần phần mềm (ví dụ: Cửa sổ, Nút, Người, Ngân hàng, Xe, v.v.)

Cũng rất phổ biến để thống nhất các khái niệm trừu tượng thành các thành phần (ví dụ: WindowListener, Broker, v.v.)


2
Reization là một khái niệm chung về "làm cho một cái gì đó thực sự" mà mặc dù nó áp dụng cho mô hình hướng đối tượng như bạn nói, cũng có một ý nghĩa trong bối cảnh thực hiện khái quát.
Jon Hanna

2
Vì vậy, tôi đã được giáo dục bằng cách đọc những câu trả lời. Tôi sẽ sửa đổi câu trả lời của tôi.
duffymo

2
Câu trả lời này không có gì để giải quyết mối quan tâm của OP đối với thuốc generic và đa hình tham số.
Erick G. Hagstrom

Nhận xét này không có gì để giải quyết sự quan tâm của bất cứ ai hoặc thúc đẩy đại diện của bạn. Tôi thấy bạn cung cấp bất cứ điều gì. Của tôi là câu trả lời đầu tiên, và nó đã định nghĩa sự thống nhất là một cái gì đó rộng hơn.
duffymo

1
Câu trả lời của bạn có thể là câu hỏi đầu tiên, nhưng bạn đã trả lời một câu hỏi khác, không phải câu hỏi của OP, điều này sẽ rõ ràng từ nội dung câu hỏi và các thẻ của nó. Có thể bạn đã không đọc kỹ câu hỏi trước khi viết câu trả lời hoặc có thể bạn không biết rằng thuật ngữ "thống nhất" có ý nghĩa thiết lập trong bối cảnh chung chung. Dù bằng cách nào, câu trả lời của bạn không hữu ích. Downvote.
jcsahnwaldt phục hồi Monica
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.