2009-06-17 30 views
13

Tôi chỉ gặp một vài thứ khá kỳ lạ với tôi: khi bạn sử dụng phương thức Equals() trên một loại giá trị (và nếu phương pháp này không được overriden, tất nhiên) bạn nhận được một cái gì đó rất rất. một đến một bằng cách sử dụng sự phản ánh! Như trong:C# - Phương thức kiểu giá trị bằng - tại sao trình biên dịch sử dụng sự phản chiếu?

public struct MyStruct{ 
    int i; 
} 

    (...) 

    MyStruct s, t; 
    s.i = 0; 
    t.i = 1; 
    if (s.Equals(t)) /* s.i will be compared to t.i via reflection here. */ 
     (...) 

Câu hỏi của tôi: tại sao trình biên dịch C# không tạo ra một phương pháp đơn giản để so sánh các loại giá trị? Một cái gì đó tương tự (trong định nghĩa MyStruct của):

public override bool Equals(Object o){ 
     if (this.i == o.i) 
     return true; 
     else 
     return false; 
    } 

Trình biên dịch biết các lĩnh vực MyStruct tại thời gian biên dịch là gì, tại sao nó đợi cho đến khi thời gian chạy để liệt kê các lĩnh vực MyStruct?

Rất lạ đối với tôi.

Cảm ơn :)

THÊM: Xin lỗi, tôi chỉ nhận ra rằng, tất nhiên, Equals không phải là một từ khóa ngôn ngữ nhưng một phương pháp runtime ... Trình biên dịch là hoàn toàn không biết gì về phương pháp này. Vì vậy, nó tạo cảm giác ở đây để sử dụng sự phản chiếu.

+0

xem "Để sử dụng triển khai chuẩn của Equals, kiểu giá trị của bạn phải được đóng hộp và thông qua như là một thể hiện của loại tài liệu tham khảo System.ValueType. Các Bằng phương pháp sau đó sử dụng phản ánh để thực hiện các so sánh. " - msdn.microsoft.com/en-us/library/ff647790.aspx – MrPhil

Trả lời

8

Sau đây là phương pháp ValueType.Equals decompiled từ mscorlib:

public override bool Equals(object obj) 
{ 
    if (obj == null) 
    { 
     return false; 
    } 
    RuntimeType type = (RuntimeType) base.GetType(); 
    RuntimeType type2 = (RuntimeType) obj.GetType(); 
    if (type2 != type) 
    { 
     return false; 
    } 
    object a = this; 
    if (CanCompareBits(this)) 
    { 
     return FastEqualsCheck(a, obj); 
    } 
    FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); 
    for (int i = 0; i < fields.Length; i++) 
    { 
     object obj3 = ((RtFieldInfo) fields[i]).InternalGetValue(a, false); 
     object obj4 = ((RtFieldInfo) fields[i]).InternalGetValue(obj, false); 
     if (obj3 == null) 
     { 
      if (obj4 != null) 
      { 
       return false; 
      } 
     } 
     else if (!obj3.Equals(obj4)) 
     { 
      return false; 
     } 
    } 
    return true; 
} 

Khi có thể, một so sánh bit-khôn ngoan sẽ được thực hiện (lưu ý các CanCompareBits và FastEqualsCheck, cả hai đều được định nghĩa là InternalCall. JIT có lẽ sẽ tiêm mã thích hợp ở đây. hy nó ​​quá chậm, tôi không thể nói với bạn.

+0

Chỉ cần kiểm tra điều này. Bạn đúng rồi. :) – SRO

+2

Tôi tự hỏi nếu có bất kỳ vấn đề tương thích nào nếu thời gian chạy là tự động tạo ra một 'Equals' override cho bất kỳ cấu trúc nào chưa định nghĩa một:' bool Equals (đối tượng khác) {return StructComparer .EqualsProc (ref this, other); } ', trong đó' EqualsProc' là một trường đại biểu tĩnh trong lớp tĩnh 'StructComparer '? Cách tiếp cận như vậy sẽ tránh phải sử dụng Reflection mỗi khi một đối tượng được so sánh, và cũng có thể tránh được một bước boxing. – supercat

9

Nó không sử dụng phản chiếu khi không cần phải. Nó chỉ so sánh các giá trị từng chút một trong trường hợp struct nếu nó có thể làm như vậy. Tuy nhiên, nếu có bất kỳ thành viên nào trong số struct (hoặc thành viên của các thành viên, bất kỳ hậu duệ nào) ghi đè object.Equals và cung cấp triển khai của riêng nó, rõ ràng là không thể dựa vào so sánh từng bit để tính giá trị trả lại.

Lý do khiến thông số chậm là tham số Equals thuộc loại object và loại giá trị phải được đóng hộp để được coi là object. Boxing liên quan đến việc cấp phát bộ nhớ trên bộ nhớ heap và bộ nhớ sao chép kiểu giá trị cho vị trí đó.

Bạn bằng tay có thể cung cấp một tình trạng quá tải cho các Equals phương pháp mà có của riêng struct như tham số để ngăn chặn boxing:

public bool Equals(MyStruct obj) { 
    return obj.i == i; 
} 
+4

Nó sử dụng sự phản chiếu trong một số trường hợp. Nếu nó phát hiện nó có thể chỉ cần blit kết quả, nó làm như vậy - nhưng nếu có các loại tài liệu tham khảo (hoặc các loại có chứa các loại tài liệu tham khảo) trong các lĩnh vực, nó phải làm một quá trình đau đớn hơn. –

+1

Trong khi tôi đang viết bài này, tôi đọc xung quanh và tôi thấy rằng một số tác giả khung .Net (Cwalina, Abrams) vừa xác nhận rằng Equals đang sử dụng sự phản chiếu trên các kiểu giá trị. Nhưng có lẽ chỉ trong Framework 2.0? – SRO

+2

Sylvain: Họ đúng. Như Jon đã nói, nếu cấu trúc chứa các kiểu tham chiếu là các thành viên, nó phải gọi Equals trên các trường đó. Tôi đã cập nhật câu trả lời để phản ánh điều đó. Điểm tôi cố gắng tạo ra là nó không sử dụng sự phản chiếu khi nó không cần (như ví dụ của bạn). –

3

Ý tưởng về chức năng tạo ra trình biên dịch là hợp lý.

Suy nghĩ về các hiệu ứng tuy nhiên tôi nghĩ nhóm thiết kế ngôn ngữ đã làm đúng. Các phương pháp liên hợp được biết đến từ C++ rất khó hiểu đối với người mới bắt đầu. Cho phép xem những gì sẽ xảy ra trong C# với struct.Equals autogenerated:

Như hiện nay, khái niệm về Equals() rất đơn giản:

  • Mỗi kế thừa struct Equals từ ValueType.
  • Nếu ghi đè, phương thức tùy chỉnh bằng phương thức áp dụng.

Nếu trình biên dịch sẽ luôn luôn tạo ra các phương pháp Equals, chúng ta có thể có:

  • Mỗi kế thừa struct Equals từ Object. (ValueType sẽ không còn thực hiện phiên bản riêng của mình)
  • Object.equals tại là luôn (!) Overriden, hoặc là bởi trình biên dịch tạo Bằng phương pháp hoặc bởi việc thực hiện sử dụng

Bây giờ struct chúng tôi có một phương pháp ghi đè autogenerated mà trình đọc mã không thấy! Vì vậy, làm thế nào để bạn biết rằng phương pháp cơ sở Object.Equals không áp dụng cho cấu trúc của bạn? Bằng cách học tất cả các trường hợp của phương pháp tự động tạo ra trình biên dịch. Và đây chính là một trong những gánh nặng học tập C++.

Hãy xem xét quyết định tốt để rời khỏi cấu trúc hiệu quả Bằng với người dùng và giữ cho các khái niệm đơn giản, yêu cầu phương thức Equals mặc định chuẩn.

Điều đó nói rằng, các cấu trúc quan trọng về hiệu năng nên ghi đè Bằng. Đoạn mã dưới đây trình bày

vs 53 mili giây đo trên Net 4.5.1

đạt được hiệu suất này chắc chắn là do tránh Equals ảo, nhưng dù sao, vì vậy nếu Object.equals ảo sẽ là được gọi là mức tăng sẽ thấp hơn nhiều. Tuy nhiên, các trường hợp quan trọng về hiệu năng sẽ không gọi Object.Equals, do đó, việc đạt được ở đây sẽ áp dụng.

using System; 
using System.Diagnostics; 

struct A 
{ 
    public int X; 
    public int Y; 
} 

struct B : IEquatable<B> 
{ 
    public bool Equals(B other) 
    { 
     return this.X == other.X && this.Y == other.Y; 
    } 

    public override bool Equals(object obj) 
    { 
     return obj is B && Equals((B)obj); 
    } 

    public int X; 
    public int Y; 
} 


class Program 
{ 
    static void Main(string[] args) 
    { 
     var N = 100000000; 

     A a = new A(); 
     a.X = 73; 
     a.Y = 42; 
     A aa = new A(); 
     a.X = 173; 
     a.Y = 142; 

     var sw = Stopwatch.StartNew(); 
     for (int i = 0; i < N; i++) 
     { 
      if (a.Equals(aa)) 
      { 
       Console.WriteLine("never ever"); 
      } 
     } 
     sw.Stop(); 
     Console.WriteLine(sw.ElapsedMilliseconds); 

     B b = new B(); 
     b.X = 73; 
     b.Y = 42; 
     B bb = new B(); 
     b.X = 173; 
     b.Y = 142; 

     sw = Stopwatch.StartNew(); 
     for (int i = 0; i < N; i++) 
     { 
      if (b.Equals(bb)) 
      { 
       Console.WriteLine("never ever"); 
      } 
     } 
     sw.Stop(); 
     Console.WriteLine(sw.ElapsedMilliseconds); 
    } 
} 

cũng http://blog.martindoms.com/2011/01/03/c-tip-override-equals-on-value-types-for-better-performance/

+0

Cần lưu ý rằng trình biên dịch không sử dụng Reflection; nó chỉ đơn giản là sử dụng phương thức ảo gửi đến phương thức 'ValueType.Equals'; vì phương thức đó hy vọng 'this' là một kiểu lớp [' ValueType' là, mặc dù tên của nó, một lớp] giá trị cần phải được đóng hộp. Về mặt khái niệm, nó có thể được tốt đẹp nếu 'ValueType' đã định nghĩa một phương thức tĩnh' ValueTypeEquals (ref T it, Object khác) {ValueTypeComparer .Compare (ref, other); 'và khuyến nghị rằng khi trình biên dịch có thể gọi là ưu tiên với phương thức 'Equals' ảo. Cách tiếp cận như vậy có thể có ... – supercat

+0

... bật thời gian chạy để tạo ra một bộ so sánh cho mỗi loại chỉ lần đầu tiên nó được sử dụng, và sau đó truy cập bộ so sánh đó trực tiếp trên các lời gọi tiếp theo. – supercat

+1

Nitpick: Cuộc gọi 'ReferenceEquals (null, obj)' là không cần thiết về mặt kỹ thuật vì biểu thức 'is' được đánh giá là false nếu biểu thức được cung cấp (' obj') là null] (https://msdn.microsoft.com/vi -us/library/scekt9xw.aspx). Tôi chắc rằng nó không ảnh hưởng đến kết quả của điểm chuẩn theo bất kỳ cách nào hữu ích. Tuy nhiên, tôi sẽ không dựa vào trình biên dịch để tối ưu hóa nó ra nếu nó sẽ có vấn đề. – tne

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