2014-11-04 18 views
7

Nếu một cố gắng để tạo ra một cấu trúc chung với thuộc tính [ StructLayout (LayoutKind .Explicit)], sử dụng các cấu trúc tạo ra một ngoại lệ trong thời gian chạy:Tại sao các loại chung chung không có bố cục rõ ràng?

System.TypeLoadException: Không thể nạp kiểu 'foo' từ 'bar' lắp ráp vì kiểu generic không thể có bố cục rõ ràng.

Tôi đã gặp khó khăn khi tìm bất kỳ bằng chứng nào cho thấy hạn chế này thậm chí còn tồn tại. Tài liệu Type.IsExplicitLayout mạnh mẽ ngụ ý rằng nó được cho phép và hỗ trợ. Có ai biết tại sao điều này không được phép? Tôi không thể nghĩ ra bất kỳ lý do gì tại sao các loại chung loại sẽ làm cho nó ít có thể kiểm chứng được. Nó đánh tôi như là một trường hợp cạnh mà họ chỉ đơn giản là không bận tâm để thực hiện.

Dưới đây là an example tại sao rõ ràng bố trí chung sẽ có ích:

public struct TaggedUnion<T1,T2> 
{ 
    public TaggedUnion(T1 value) { _union=new _Union{Type1=value}; _id=1; } 
    public TaggedUnion(T2 value) { _union=new _Union{Type2=value}; _id=2; } 

    public T1 Type1 { get{ if(_id!=1)_TypeError(1); return _union.Type1; } set{ _union.Type1=value; _id=1; } } 
    public T2 Type2 { get{ if(_id!=2)_TypeError(2); return _union.Type2; } set{ _union.Type2=value; _id=2; } } 

    public static explicit operator T1(TaggedUnion<T1,T2> value) { return value.Type1; } 
    public static explicit operator T2(TaggedUnion<T1,T2> value) { return value.Type2; } 
    public static implicit operator TaggedUnion<T1,T2>(T1 value) { return new TaggedUnion<T1,T2>(value); } 
    public static implicit operator TaggedUnion<T1,T2>(T2 value) { return new TaggedUnion<T1,T2>(value); } 

    public byte Tag {get{ return _id; }} 
    public Type GetUnionType() {switch(_id){ case 1:return typeof(T1); case 2:return typeof(T2); default:return typeof(void); }} 

    _Union _union; 
    byte _id; 
    void _TypeError(byte id) { throw new InvalidCastException(/* todo */); } 

    [StructLayout(LayoutKind.Explicit)] 
    struct _Union 
    { 
     [FieldOffset(0)] public T1 Type1; 
     [FieldOffset(0)] public T2 Type2; 
    } 
} 

sử dụng:

TaggedUnion<int, double> foo = 1; 
Debug.Assert(foo.GetUnionType() == typeof(int)); 
foo = 1.0; 
Debug.Assert(foo.GetUnionType() == typeof(double)); 
double bar = (double) foo; 

Edit:

Để được rõ ràng, lưu ý rằng bố trí không được xác nhận tại thời gian biên dịch ngay cả khi cấu trúc không phải là chung chung. Sự khác nhau về tham chiếu chồng lên nhau và x64 được phát hiện trong thời gian chạy bởi CLR: http://pastebin.com/4RZ6dZ3S Tôi hỏi tại sao generics bị hạn chế khi kiểm tra được thực hiện khi chạy theo một trong hai cách.

Trả lời

4

Gốc của vấn đề là tính tổng quát và tính xác thực và thiết kế dựa trên các ràng buộc về loại. Quy tắc mà chúng tôi không thể chồng chéo tham chiếu (con trỏ) với các loại giá trị là một ràng buộc đa tham số, ngầm định. Vì vậy, chúng tôi biết CLR là đủ thông minh để xác minh điều này trong trường hợp không chung chung ... tại sao không chung chung? Âm thanh hấp dẫn.

Định nghĩa loại chung chung chính xác là định nghĩa loại chính xác hiện hoạt động cho bất kỳ loại nào tồn tại (trong các ràng buộc) và bất kỳ loại nào sẽ được xác định trong tương lai. [1] CLR qua C#, Richter Trình biên dịch xác minh định nghĩa kiểu generic mở theo cách riêng của nó, xem xét bất kỳ ràng buộc kiểu nào bạn chỉ định để thu hẹp các đối số kiểu có thể.

Khi không có ràng buộc loại cụ thể hơn, đối với Foo<T,U>, T và U thể hiện cả liên minh của tất cả giá trị và loại tham chiếu có thể và interface chung cho tất cả các loại đó (cơ sở System.Object). Nếu chúng ta muốn làm cho T hoặc U cụ thể hơn, chúng ta có thể thêm các ràng buộc kiểu chính và phụ. Trong phiên bản mới nhất của C#, cụ thể nhất chúng ta có thể hạn chế là một lớp hoặc một giao diện. cấu trúc hoặc các ràng buộc kiểu nguyên thủy không được hỗ trợ.

Chúng tôi hiện không thể nói:

  1. nơi chỉ struct hoặc value type
  2. nơi T nếu T là một loại kín

Ex:

public struct TaggedUnion<T1, T2> 
    where T1 : SealedThing // illegal 

vì vậy chúng tôi không có cách xác định loại chung chung có thể xác minh để không bao giờ vi phạm quy tắc chồng chéo cho tất cả các loại trong số TU. Ngay cả khi chúng ta có thể hạn chế bởi cấu trúc, bạn vẫn có thể lấy được một cấu trúc với các trường tham chiếu sao cho một số loại trong tương lai, T<,> sẽ không chính xác.

Vì vậy, những gì chúng tôi thực sự yêu cầu ở đây là why don't generic types allow implicit type constraints based on code within the class?; bố cục rõ ràng là chi tiết triển khai nội bộ áp đặt các hạn chế về việc các kết hợp của T1T2 là hợp pháp. Theo tôi, điều đó không nhất quán với thiết kế phụ thuộc vào các ràng buộc kiểu. Nó vi phạm hợp đồng sạch của hệ thống kiểu chung như được thiết kế. Vậy tại sao thậm chí phải trải qua những rắc rối khi áp đặt một hệ thống hạn chế kiểu trong thiết kế ngay từ đầu, nếu chúng ta định phá vỡ nó? Chúng tôi cũng có thể ném nó ra và thay thế nó bằng ngoại lệ.

Với tình trạng hiện thời của sự vật:

  1. Loại hạn chế là siêu dữ liệu hữu hình của kiểu generic mở
  2. Thẩm định kiểu generic Foo<T,U> được thực hiện trên định nghĩa mở F<,> một lần. Đối với mỗi thể loại kiểu ràng buộc của Foo<t1,u1>, t1 và u1 được kiểm tra về tính chính xác của loại đối với các ràng buộc. Không cần phải xác minh lại mã cho lớp và các phương thức cho Foo<t1,u1>.

Tất cả điều này là "As far as I Know"

Không có lý do kỹ thuật cứng tại sao tất cả các loại instantiation generic không thể ngữ nghĩa phân tích đúng đắn (C++ là bằng chứng về điều đó) nhưng nó sẽ có vẻ để phá vỡ thiết kế tại chỗ.

TL; DR

Nếu không phá vỡ hoặc bổ sung các thiết kế loại hạn chế đang tồn tại không có cách nào cho điều này là có thể kiểm chứng.

Có lẽ, kết hợp với các ràng buộc loại mới thích hợp, chúng ta có thể thấy nó trong tương lai.

+0

Đối với hồ sơ, cùng một loại "áp đặt hạn chế" có thể được thực hiện bằng cách ném từ các nhà xây dựng tĩnh. (Và tôi sẽ không ngạc nhiên khi thấy điều đó được thực hiện trong thực tế vì những hạn chế chung là vô lý.) Nhưng tôi đoán tôi sẽ không ngạc nhiên nếu đó thực sự là lý do của họ cho giới hạn này. – DBN

+0

@DBN - Tôi đoán đó thực sự là lý do, nếu không người ta có thể lập luận rằng hệ thống ràng buộc kiểu có thể được tung ra thay cho ngoại lệ. Tôi cũng đồng ý với bạn, họ không đủ mạnh. Về mặt tươi sáng, việc triển khai C#/CTS dễ viết hơn nhiều so với C++. Là một nhà biên dịch trình biên dịch, tôi thực tế có thể có cơ hội thực hiện ngôn ngữ của riêng tôi với các CTS generics mà không cần sự trợ giúp. Với các mẫu C++, bạn nên có một nhóm với bạn. Tôi phải thừa nhận, C# là một thiết kế tương đối sạch sẽ. – codenheim

+0

Tôi không thấy bất kỳ đường dẫn tốt nào để thêm bất kỳ loại ràng buộc chung nào vào hệ thống kiểu, mà giả định một tập hợp rất cố định của những người không kế thừa [mới, cấu trúc và lớp]. Mặc dù có thể hữu ích khi có một hạn chế cho ví dụ: "có thể sao chép thành giá trị" (làm cho nó có thể định nghĩa các cấu trúc * không *), không có ràng buộc chung nào hiện đang yêu cầu nó, và do đó không có kiểu generic nào có thể thỏa mãn nó. Điều có lẽ sẽ là cần thiết để có một hạn chế chống lại, như vậy mà các cấu trúc không được sao chép-như-giá trị có thể * chỉ * được chuyển cho các tham số chung ... – supercat

9

Nó quy định tại ECMA 335 (CLI), phân vùng II, phần II.10.1.2:

rõ ràng: Cách bố trí của các lĩnh vực được quy định một cách rõ ràng (§II.10.7). Tuy nhiên, loại chung sẽ không có bố cục rõ ràng.

Bạn có thể tưởng tượng nó có thể khó xử như thế nào - với kích thước của thông số loại phụ thuộc vào thông số loại, bạn có thể nhận được một số hiệu ứng quyết định kỳ quặc ... trường tham chiếu không được phép chồng lên nhau ví dụ: kiểu giá trị được tích hợp hoặc một tham chiếu khác, sẽ khó có thể đảm bảo ngay khi các kích thước không xác định được tham gia. (Tôi chưa xem xét cách thức hoạt động của tài liệu tham khảo 32 bit so với 64 bit, có vấn đề tương tự nhưng hơi khác ...)

Tôi nghi ngờ đặc tả này có thể được viết để làm cho một số chi tiết hơn hạn chế - nhưng làm cho nó một hạn chế chăn đơn giản trên tất cả các loại generic là đơn giản hơn đáng kể.

+1

Cấu trúc thông thường có thể thay đổi theo thời gian chạy. (Xem: IntPtr) Ngay cả khi nó không phải là chung chung, xác minh rằng không có sự chồng chéo giữa tham chiếu và các loại giá trị đã được xử lý độc quyền bởi CLR: http://pastebin.com/4RZ6dZ3S "Những hạn chế chi tiết hơn" sẽ cần đến được làm cho generics nếu quy tắc đã được dỡ bỏ? – DBN

+0

@DBN Tôi tự hỏi tại sao có một quy tắc về các loại chung, sau đó, khi CLR kiểm tra mọi loại khi được tải. – IllidanS4

+0

@ IllidanS4 Vâng, đó là những gì tôi đang hỏi. – DBN

2

Thiết kế của.NET framework làm cho một số giả định về các kiểu generic sẽ làm cho nó về cơ bản không thể cho phép một cấu trúc bố cục rõ ràng để có bất kỳ trường nào có kích thước có thể khác nhau dựa trên các tham số kiểu generic. Có lẽ cơ bản nhất là:

  • Nếu có tồn tại bất kỳ sự kết hợp của các đối số kiểu mà một định nghĩa kiểu generic sẽ có giá trị, nó có thể được coi là hợp lệ cho tất cả các kết hợp của các đối số mà đáp ứng hạn chế theo quy định của nó.

Do đó, .NET không cho phép cấu trúc bố cục rõ ràng sử dụng loại chung chung không bị giới hạn ở class làm loại trường khác với trường cuối cùng. Hơn nữa, việc sử dụng kiểu giá trị chung mà sử dụng kiểu chung chung được truyền vào làm đối số phải bị ràng buộc giống như kiểu được truyền vào.

Tôi không nghĩ sẽ có bất kỳ vấn đề cụ thể với phép một cấu trúc rõ ràng, bố trí để có một đẳng cấp hạn chế tham số kiểu generic nếu được phép che phủ mọi lĩnh vực của kiểu hay của bất kỳ giá trị loại được sử dụng như một đối số kiểu, và một trường như vậy, nếu không biết là kiểu tham chiếu, phải là điều cuối cùng trong cấu trúc. Mặt khác, hầu hết các trường hợp sử dụng "an toàn" có thể được xử lý tốt hơn bằng cách có cấu trúc bố cục không rõ ràng chứa loại chung và có một hoặc nhiều cấu trúc bố cục rõ ràng nằm trong đó. Cách tiếp cận như vậy có thể làm mọi thứ có thể được thực hiện với các cấu trúc bố cục rõ ràng. Sự phiền toái duy nhất sẽ phải thêm một mức không phụ thuộc vào mã nguồn khi truy cập các thành viên lồng nhau, và biện pháp khắc phục sẽ không cho phép các cấu trúc bố cục rõ ràng chung, mà là cung cấp một phương tiện mà qua đó một cấu trúc chứa cấu trúc khác có thể tạo bí danh cho các thành viên cấu trúc bên trong.

Như một ví dụ:

[StructLayout(LayoutKind.Explicit)] 
public struct _UnionLongAnd2Ints 
{ 
    [FieldOffset(0)] public int LowerWord; 
    [FieldOffset(4)] public int UpperWord; 
    [FieldOffset(0)] public long Value; 
} 
public struct LongTwoIntsUnionAndSomethingElse<T> 
{ 
    UnionLongAnd2Ints UnionPart; 
    T OtherPart; 
} 

Đây là cấu trúc chung chứa một giá trị 64-bit được phủ trên hai giá trị 32-bit, nhưng nó cần được kiểm chứng bởi vì phần rõ ràng-sa-ra doesn' t có bất cứ điều gì chung chung.

+0

Tôi không nghĩ rằng tôi hiểu ý của bạn về cấu trúc tường minh lồng nhau. Chúng sẽ chứa những gì? Có khác gì so với cấu trúc bố cục rõ ràng lồng nhau trong ví dụ của tôi không? (_Union) – DBN

+0

@DBN: Xem ví dụ ở trên. – supercat

Các vấn đề liên quan