Vấn đề hiểu hiệp phương sai với phương pháp chung trong C #


115

Tôi không thể hiểu tại sao mã C # sau không biên dịch.

Như bạn có thể thấy, tôi có một phương thức chung tĩnh Một cái gì đó với một IEnumerable<T>tham số (và Tbị ràng buộc là một IAgiao diện) và tham số này không thể được chuyển đổi hoàn toàn thành IEnumerable<IA>.

Giải thích là gì? (Tôi không tìm kiếm cách giải quyết, chỉ để hiểu lý do tại sao nó không hoạt động).

public interface IA { }
public interface IB : IA { }
public class CIA : IA { }
public class CIAD : CIA { }
public class CIB : IB { }
public class CIBD : CIB { }

public static class Test
{
    public static IList<T> Something<T>(IEnumerable<T> foo) where T : IA
    {
        var bar = foo.ToList();

        // All those calls are legal
        Something2(new List<IA>());
        Something2(new List<IB>());
        Something2(new List<CIA>());
        Something2(new List<CIAD>());
        Something2(new List<CIB>());
        Something2(new List<CIBD>());
        Something2(bar.Cast<IA>());

        // This call is illegal
        Something2(bar);

        return bar;
    }

    private static void Something2(IEnumerable<IA> foo)
    {
    }
}

Lỗi tôi nhận được trong Something2(bar)dòng:

Đối số 1: không thể chuyển đổi từ 'System.Collections.Generic.List' thành 'System.Collections.Generic.IEnumerable'



12
Bạn đã không giới hạn Tcác loại tài liệu tham khảo. Nếu bạn sử dụng điều kiện where T: class, IAthì nó sẽ hoạt động. Câu trả lời liên kết có nhiều chi tiết hơn.
Dirk

2
@Dirk Tôi không nghĩ rằng điều này nên được gắn cờ là một bản sao. Mặc dù đúng là vấn đề khái niệm ở đây là vấn đề hiệp phương sai / đối kháng khi đối mặt với các loại giá trị, trường hợp cụ thể ở đây là "thông báo lỗi này có nghĩa gì" cũng như tác giả không nhận ra chỉ bao gồm "lớp" khắc phục vấn đề của mình. Tôi tin rằng người dùng trong tương lai sẽ tìm kiếm thông báo lỗi này, tìm bài đăng này và để lại hạnh phúc. (Như tôi thường làm.)
Reginald Blue

Bạn cũng có thể tái tạo tình huống bằng cách nói Something2(foo);trực tiếp. Đi xung quanh .ToList()để có được một List<T>( Tlà tham số loại của bạn được khai báo theo phương thức chung) là không cần thiết để hiểu điều này (a List<T>là một IEnumerable<T>).
Jeppe Stig Nielsen

@ReginaldBlue 100%, sẽ đăng điều tương tự. Câu trả lời tương tự không phải là một câu hỏi trùng lặp.
UuDdLrLrSs

Câu trả lời:


218

Thông báo lỗi không đủ thông tin và đó là lỗi của tôi. Xin lỗi vì điều đó.

Vấn đề bạn đang gặp phải là hậu quả của thực tế là hiệp phương sai chỉ hoạt động trên các loại tham chiếu.

Bạn có thể nói "nhưng IAlà một loại tham chiếu" ngay bây giờ. Vâng, đúng vậy. Nhưng bạn đã không nói rằng T nó bằng IA . Bạn đã nói rằng đó Tlà một loại thực hiện IAmột loại giá trị có thể thực hiện một giao diện . Do đó, chúng tôi không biết liệu hiệp phương sai có hoạt động hay không và chúng tôi không cho phép.

Nếu bạn muốn hiệp phương sai hoạt động, bạn phải báo cho trình biên dịch rằng tham số loại là kiểu tham chiếu với classràng buộc cũng như IAràng buộc giao diện.

Thông báo lỗi thực sự nên nói rằng việc chuyển đổi là không thể vì hiệp phương sai yêu cầu đảm bảo tính tham chiếu kiểu tham chiếu, vì đó là vấn đề cơ bản.


3
Tại sao bạn nói đó là lỗi của bạn?
dùng4951

77
@ user4951: Bởi vì tôi đã triển khai tất cả logic kiểm tra chuyển đổi bao gồm các thông báo lỗi.
Eric Lippert

@BurnsBA Đây chỉ là một "lỗi" theo nghĩa nhân quả - việc thực hiện về mặt kỹ thuật cũng như thông báo lỗi là hoàn toàn chính xác. (Nó chỉ rằng tuyên bố lỗi của inconvertability có thể xây dựng trên những lý do thực tế Nhưng sản xuất lỗi tốt với Generics là cứng -. So với thông điệp mẫu lỗi C ++ một vài năm trước đây này là sáng suốt và súc tích.)
Peter - Khôi phục Monica

3
@ PeterA.Schneider: Tôi đánh giá cao điều đó. Nhưng một trong những mục tiêu chính của tôi khi thiết kế logic báo cáo lỗi ở Roslyn là đặc biệt nắm bắt không chỉ quy tắc nào đã bị vi phạm, mà hơn nữa, để xác định "nguyên nhân gốc" nếu có thể. Ví dụ, thông báo lỗi nên làm customers.Select(c=>c.FristName)gì? Đặc tả C # rất rõ ràng rằng đây là lỗi giải quyết quá tải : tập hợp các phương thức áp dụng có tên Chọn có thể lấy lambda trống. Nhưng nguyên nhân gốc rễ là FirstNamecó một lỗi đánh máy.
Eric Lippert

3
@ PeterA.Schneider: Tôi đã làm rất nhiều việc để đảm bảo rằng các kịch bản liên quan đến suy luận kiểu chung và lambdas đã sử dụng các phương pháp phỏng đoán thích hợp để suy ra thông báo lỗi nào có khả năng giúp nhà phát triển tốt nhất. Nhưng tôi đã làm rất ít về các thông báo lỗi chuyển đổi, đặc biệt là vấn đề về phương sai. Tôi đã luôn hối hận vì điều đó.
Eric Lippert

26

Tôi chỉ muốn bổ sung cho câu trả lời nội bộ xuất sắc của Eric bằng một ví dụ mã cho những người có thể không quen thuộc với các ràng buộc chung.

Thay đổi Somethingchữ ký như thế này: classRàng buộc phải đến trước .

public static IList<T> Something<T>(IEnumerable<T> foo) where T : class, IA

2
Tôi tò mò ... chính xác lý do đằng sau ý nghĩa của việc đặt hàng là gì?
Tom Wright

5
@TomWright - tất nhiên, thông số kỹ thuật không bao gồm câu trả lời cho nhiều câu "Tại sao?" câu hỏi, nhưng trong trường hợp này làm rõ rằng có ba loại ràng buộc riêng biệt và khi cả ba loại được sử dụng, chúng phải được đặc biệtprimary_constraint ',' secondary_constraints ',' constructor_constraint
Damien_The_Unbeliever

2
@TomWright: Damien là chính xác; không có lý do cụ thể nào tôi biết ngoài sự tiện lợi của tác giả của trình phân tích cú pháp. Nếu tôi có máy khoan của mình, cú pháp cho các ràng buộc kiểu sẽ dài hơn đáng kể . classlà xấu vì nó có nghĩa là "loại tham chiếu", không phải "lớp". Tôi sẽ hạnh phúc hơn với thứ gì đó dài dòng nhưwhere T is not struct
Eric Lippert
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.