2009-07-13 19 views
33

Các mã sau đây:"Giao diện không được thực hiện" khi trở về nguồn gốc Loại

public interface ISomeData 
{ 
    IEnumerable<string> Data { get; } 
} 

public class MyData : ISomeData 
{ 
    private List<string> m_MyData = new List<string>(); 
    public List<string> Data { get { return m_MyData; } } 
} 

Tạo các lỗi sau:

error CS0738: 'InheritanceTest.MyData' does not implement interface member 'InheritanceTest.ISomeData.Data'. 'InheritanceTest.MyData.Data' cannot implement 'InheritanceTest.ISomeData.Data' because it does not have the matching return type of 'System.Collections.Generic.IEnumerable'.

Từ một danh sách <T> thực hiện IEnumerable <T>, người ta sẽ nghĩ rằng lớp của tôi sẽ thực hiện giao diện. Ai đó có thể giải thích lý do cơ bản cho việc này không biên dịch?

Như tôi đã có thể nhìn thấy nó, có hai giải pháp khả thi:

  1. Thay đổi giao diện để cụ thể hơn và đòi hỏi IList được thực hiện.
  2. Thay đổi lớp học của tôi (MyData) để trả về IEnumerable và triển khai giao diện gốc.

Bây giờ giả sử tôi cũng có đoạn mã sau:

public class ConsumerA 
{ 
    static void IterateOverCollection(ISomeData data) 
    { 
     foreach (string prop in data.MyData) 
     { 
      /*do stuff*/ 
     } 
    } 
} 

public class ConsumerB 
{ 
    static void RandomAccess(MyData data) 
    { 

     data.Data[1] = "this line is invalid if MyPropList return an IEnumerable<string>"; 
    } 
} 

tôi có thể thay đổi giao diện của tôi để yêu cầu IList được thực hiện (phương án 1), nhưng điều đó hạn chế những người có thể thực hiện các giao diện và số lượng các lớp học có thể được chuyển vào ConsumerA. Hoặc, tôi có thể thay đổi việc thực hiện (lớp MyData) để nó trả về một IEnumerable thay vì một List (tùy chọn 2), nhưng sau đó ConsumerB sẽ phải được viết lại.

Điều này có vẻ là một thiếu sót của C# trừ khi ai đó có thể khai sáng cho tôi.

+0

.. nó không thực sự là một thiếu sót, chỉ là một ví dụ khác của C# chỉ làm những gì bạn nói với nó. VB.NET có rất nhiều tình huống nhỏ như thế này (không chắc chắn về điều này cụ thể mặc dù), nơi họ trình biên dịch sẽ nói "Tôi biết những gì bạn * thực sự * muốn" và sẽ không lỗi bạn thẳng nó ra; C# sẽ nói "Tôi không quan tâm những gì bạn muốn, tất cả những gì tôi biết là những gì bạn đang nói với tôi không bay." – STW

+5

Tôi vẫn không đồng ý và nghĩ rằng đó là một thiếu sót. Giao diện nói "bạn phải cung cấp dữ liệu có thể được liệt kê trên" và lớp của tôi có nội dung "đây là dữ liệu mà bạn có thể liệt kê trên VÀ thực hiện truy cập ngẫu nhiên trên AND v.v.". Tuy nhiên, trình biên dịch nói rằng "bạn không hoàn thành hợp đồng!". – Daniel

+3

@Daniel, nhưng nếu giao diện chỉ chỉ định rằng bạn cung cấp dữ liệu có thể được liệt kê hơn thì nó sẽ không bao giờ cho phép người gọi tận dụng tất cả các tính năng khác. Bằng cách giới hạn bạn phơi bày * chỉ * một đối tượng tương thích với IEnumerable, điều này khiến bạn nhận thấy rằng bất kỳ giá trị gia tăng nào khác của Danh sách ** sẽ không bao giờ có sẵn cho người gọi. Nó làm cho bạn lương tâm về điều đó. Nó giúp bạn không đến đây vào ngày mai và hỏi "MyData.Data cho thấy một Danh sách , nhưng khi một lớp tiêu thụ làm việc với nó, tôi không thể gọi .Thêm()! WTC! C# là stoopid!" – STW

Trả lời

20

Thật không may, kiểu trả về phải phù hợp. Những gì bạn đang tìm kiếm được gọi là 'return type covariance' và C# không hỗ trợ điều đó.

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=90909

Eric Lippert, nhà phát triển cấp cao trong nhóm C# Compiler, đề cập trên blog của mình rằng họ không có kế hoạch để hỗ trợ kiểu trả về hiệp phương sai.

"That kind of variance is called "return type covariance". As I mentioned early on in this series, (a) this series is not about that kind of variance, and (b) we have no plans to implement that kind of variance in C#. "

http://blogs.msdn.com/ericlippert/archive/2008/05/07/covariance-and-contravariance-part-twelve-to-infinity-but-not-beyond.aspx

Đó là giá trị đọc bài viết của Eric trên hiệp phương sai và contravariance.

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

+0

Tại sao điều này không được hỗ trợ? Có vẻ hợp lý? –

1

Giao diện yêu cầu chữ ký của phương thức khớp với chữ ký của hợp đồng chính xác.

Dưới đây là một ví dụ đơn giản đó cũng sẽ không biên dịch:

interface IFoo 
{ 
    Object GetFoo(); 
} 

class Foo : IFoo 
{ 
    public String GetFoo() { return ""; } 
} 

Bây giờ như đối với những việc cần làm về nó tôi sẽ cho phép giao diện ra lệnh thực hiện. Nếu bạn muốn hợp đồng là IEnumerable<T> thì đó cũng là điều nên làm trong lớp học. Giao diện là điều quan trọng nhất ở đây vì việc triển khai tự do linh hoạt như nó cần.

Chỉ cần chắc chắn rằng IEnumerable<T> là lựa chọn tốt nhất tại đây. (Điều này rất quan trọng vì tôi không biết nhiều về miền của bạn hoặc mục đích của các loại này trong ứng dụng của bạn. Chúc may mắn!)

0

Điều gì sẽ xảy ra nếu bạn thay đổi giao diện để mở rộng IEnumerable để bạn có thể liệt kê đối tượng và chỉnh sửa dữ liệu thông qua thuộc tính lớp.

public interface ISomeData : IEnumerable<string> 
{ 
    IEnumerable<string> Data { get; } 
} 

public class MyData : ISomeData 
{ 
    private List<string> m_MyData = new List<string>(); 
    public List<string> Data { get { return m_MyData; } 

    public IEnumerator<string> GetEnumerator() 
    { 
     return Data; 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 
3

Chữ ký của thành viên không thể khác nhau.

Bạn vẫn có thể trả lại List<string> trong phương thức get, nhưng chữ ký cần phải giống với giao diện.

Vì vậy, chỉ cần thay đổi:

public List<string> Data { get { return m_MyData; } } 

để

public IEnumerable<string> Data { get { return m_MyData; } } 

Về lựa chọn nào khác: thay đổi giao diện để trả về một List. Điều này nên tránh. Nó là người nghèo encapsulation và được coi là một code smell.

+0

Bằng cách này, bạn sẽ giới hạn bất kỳ ai sử dụng lớp MyData trực tiếp để chỉ tương tác với Dữ liệu theo nhiều cách. Điều này khá hạn chế. (Trừ khi người dùng quay trở lại Danh sách, nhưng đó sẽ là một quyết định rất kém). – Daniel

+2

Có, đây là hạn chế, và đó là một điều tốt (tm). Các phương pháp khác sẽ cho phép sửa đổi dữ liệu nếu nó được yêu cầu. Trả về danh sách sẽ cho phép các lớp khác quản lý cấu trúc nội bộ của lớp MyData. Điều này dẫn đến tính ghen tị và sẽ là một triển khai kém. Nếu cần sửa đổi cấu trúc dữ liệu, MyData sẽ làm điều đó, không có ai khác. –

0

Bạn có thể thực hiện như thế này:

public class MyData : ISomeData 
{ 
    private List<string> m_MyData = new List<string>(); 
    public IEnumerable<string> Data { get { return m_MyData; } } 
} 
21

Đối với những gì bạn muốn làm có thể bạn sẽ muốn thực hiện giao diện một cách rõ ràng với một thành viên lớp (không giao tiếp) trả về danh sách thay vì IEnumerable ...

public class MyData : ISomeData 
{ 
    private List<string> m_MyData = new List<string>(); 
    public List<string> Data 
    { 
     get 
     { 
      return m_MyData; 
     } 
    } 

    #region ISomeData Members 

    IEnumerable<string> ISomeData.Data 
    { 
     get 
     { 
      return Data.AsEnumerable<string>(); 
     } 
    } 

    #endregion 
} 

Chỉnh sửa: Để làm rõ, điều này cho phép lớp MyData trả về một Danh sách khi nó đang được coi là một thể hiện của MyData; trong khi vẫn cho phép nó trả về một thể hiện của IEnumerable khi được coi là một thể hiện của ISomeData.

+0

Yooder, cảm ơn bạn đã trả lời.Tôi sẽ không gọi nó là đẹp (không phải lỗi của bạn), nhưng về mặt chức năng, nó sẽ đưa tôi đến nơi tôi muốn đi. – Daniel

1

Tại sao không chỉ trả lại một danh sách từ giao diện của bạn ...

public interface ISomeData 
{ 
    List<string> Data { get; } 
} 

Nếu bạn biết người tiêu dùng của bạn sẽ cho cả hai lặp trên nó (IEnumerable) và thêm vào nó (IList) sau đó nó có vẻ hợp lý để chỉ cần trả lại một Danh sách.

0

Hmm, có phải là thiếu sót hay không, tôi sẽ nói không.

Trong cả hai cách, tôi sẽ làm việc xung quanh nó như câu trả lời Darin, hoặc, nếu bạn muốn một cách rõ ràng một danh sách accessor là tốt, bạn có thể làm điều đó như thế này:

public class MyData : ISomeData 
{ 

    IEnumerable<string> ISomeData.Data 
    { 
     get 
     { 
       return _myData; 
     } 
    } 

    public List<string> Data 
    { 
      get 
      { 
      return (List<string>)((ISomeData)this).Data; 
      } 
    } 

} 
4

gì nếu bạn truy cập đối tượng MyData của bạn máng giao diện ISomeData? Trong trường hợp đó, IEnumerable có thể thuộc loại cơ bản không thể gán cho Danh sách.

IEnumerable<string> iss = null; 

List<string> ss = iss; //compiler error 

EDIT:

tôi hiểu những gì bạn có nghĩa là từ ý kiến ​​của bạn.

Dù sao, những gì tôi sẽ làm gì trong trường hợp của bạn sẽ là:

public interface ISomeData<T> where T: IEnumerable<string> 
    { 
     T Data { get; } 
    } 

    public class MyData : ISomeData<List<string>> 
    { 
     private List<string> m_MyData = new List<string>(); 
     public List<string> Data { get { return m_MyData; } } 
    } 

Chuyển đổi sang giao diện chung với chế thích hợp cung cấp tôi nghĩ là tốt nhất của cả hai linh hoạt và dễ đọc.

+0

Đây là câu trả lời duy nhất cho đến nay thậm chí từ xa địa chỉ lý do tại sao điều này không phải là một thiếu sót của C#. – womp

+0

Vâng, tôi cho rằng thực thi hợp đồng tốt. –

+0

Giao diện chỉ xác định quyền nhận cho thuộc tính, vì vậy không có ngữ cảnh nào bạn cho rằng việc gán một IEnumerable sẽ được cho phép. Tôi thấy những gì bạn đang nói, nhưng vì tôi đã không chỉ định một tập hợp, điểm của bạn không hợp lệ trong ví dụ này. – Daniel

0

tôi sẽ chọn phương án 2:

Điểm để xác định một giao diện trong mã của bạn là để xác định một hợp đồng, và do đó bạn và những người khác thực hiện giao diện của bạn biết những gì để thỏa thuận về. Cho dù bạn định nghĩa IEnumerable hoặc List trong giao diện của bạn thực sự là một vấn đề hợp đồng và thuộc về hướng dẫn thiết kế khung. Đây là một số whole book để thảo luận về điều này.

Cá nhân, tôi sẽ hiển thị IEnumerable và triển khai MyData thành IEnumerable và bạn có thể truyền trở lại Danh sách trong phương thức RandomAccess().

+0

Vì vậy, những gì bạn đang nói là bạn sẽ thực hiện một hợp đồng hạn chế hơn/giao diện, sau đó bạn sẽ bỏ qua nó bằng cách đúc vào một danh sách. Đó là thiết kế tồi vì bạn giả định kiến ​​thức về hoạt động bên trong của một lớp. Hơn nữa, điều này về một giao diện cho biết "ít nhất bạn phải cung cấp dữ liệu có thể được liệt kê trên" và một lớp cho biết "đây là dữ liệu mà bạn có thể liệt kê và truy cập ngẫu nhiên trên AND et et". Tuy nhiên, trình biên dịch nói rằng "bạn không hoàn thành hợp đồng!" – Daniel

+0

Không, tôi KHÔNG đề nghị bạn cố ý bỏ nó vào một loại cụ thể Danh sách nếu giao diện bạn đang thực hiện là IEnumerable. Tôi chỉ đề nghị bạn có một cách giải quyết nếu bạn đi đến option2. –

0

Nếu bạn cần để có một danh sách trong giao diện của bạn (số tiếng của mục có truy cập ngẫu nhiên), sau đó bạn nên xem xét thay đổi giao diện để

public interface ISomeData 
{ 
    ICollection<string> Data { get; } 
} 

này sẽ cung cấp cho bạn tất cả các khả năng mở rộng và các tính năng bạn cần từ danh sách.

List<T> không thể dễ dàng được phân lớp, có nghĩa là bạn có thể gặp khó khăn khi trả lại chính xác loại từ tất cả các lớp muốn triển khai giao diện của bạn.

ICollection<T> mặt khác, có thể được triển khai theo nhiều cách khác nhau.

0

Tôi đã gặp phải các tình huống tương tự và muốn đưa ra ví dụ cụ thể hơn về lý do tại sao điều này không được phép. Các phần chung của giao dịch ứng dụng của tôi với giao diện chứa các thuộc tính và cung cấp dữ liệu thông qua giao diện đó cho một phần khác của ứng dụng có chứa các lớp cụ thể triển khai các giao diện này.

Mục tiêu tôi nghĩ tương tự như của bạn: các lớp conrete có nhiều chức năng hơn và giao diện cung cấp mức tối thiểu tuyệt đối cần thiết cho phần chung của ứng dụng. Đó là một thực hành tốt cho Giao diện để hiển thị số lượng chức năng ít nhất cần thiết, vì nó tối đa hóa tính tương thích/khả năng tái sử dụng. I E. nó không đòi hỏi người triển khai giao diện thực hiện nhiều hơn là cần thiết.

Tuy nhiên, hãy cân nhắc xem bạn có thiết lập trên thuộc tính đó hay không. Bạn tạo một thể hiện của lớp cụ thể của bạn và chuyển nó cho một số trình trợ giúp chung có ISomeData. Trong mã của người trợ giúp đó, họ đang làm việc với một ISomeData. Không có bất kỳ kiểu T chung nào trong đó T: new() hoặc mẫu nhà máy, chúng không thể tạo các cá thể mới để đi vào Dữ liệu khớp với việc triển khai cụ thể của bạn. Họ chỉ cần trả lại danh sách triển khai IEnumerable:

instanceISomeData.Data = new SomeOtherTypeImplementingIEnumerable();

Nếu SomeOtherTypeImplementingIEnumerable không kế thừa Danh sách, nhưng bạn đã triển khai .Data dưới dạng Danh sách thì đây là nhiệm vụ không hợp lệ. Nếu trình biên dịch cho phép bạn thực hiện việc này, thì các tình huống như thế này sẽ bị lỗi khi chạy vì SomeOtherTypeImplementingIEnumerable không thể được truyền vào Danh sách. Tuy nhiên, trong bối cảnh của trình trợ giúp này làm việc với ISomeData, dù sao nó cũng không vi phạm giao diện ISomeData và việc gán bất kỳ kiểu nào hỗ trợ IEnumerable cho .Data nên hợp lệ. Vì vậy, thực hiện của bạn, nếu trình biên dịch cho phép nó, có thể phá vỡ mã hoàn toàn tốt làm việc với giao diện.

Đây là lý do tại sao bạn không thể triển khai .Data dưới dạng loại có nguồn gốc. Bạn đang ràng buộc chặt chẽ hơn việc triển khai .Data chỉ trừ Danh sách, thay vì bất kỳ IEnumerable nào. Do đó, trong khi giao diện cho biết "bất kỳ IEnumerable nào được cho phép", việc triển khai cụ thể của bạn sẽ khiến mã sẵn có hỗ trợ ISomeData đột nhiên bị ngắt khi làm việc với việc triển khai giao diện của bạn.

Tất nhiên bạn không thực sự gặp phải trường hợp này chỉ với một trình thu thập dữ liệu, nhưng nó sẽ làm phức tạp mọi thứ để cho phép nó có được các kịch bản nhưng không khác.

Tôi thường đi với giải pháp của Jake hoặc giải pháp của Alioto, tùy thuộc vào cảm giác khó chịu của tôi lúc này.

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