2013-08-24 42 views
5

Tôi đang cố gắng tìm hiểu cách tạo các lớp chung với C#. Ai đó có thể giải thích lý do tại sao tôi nhận được một lỗi biên dịch khi tôi chạy chương trình này.C# generics với giao diện

Tôi đã tạo giao diện IZooAnimal. Tất cả động vật sở thú sẽ thực hiện giao diện này.

public interface IZooAnimal 
{ 
    string Id { get; set; } 
} 

public class Lion : IZooAnimal 
{ 
    string Id { get; set; } 
} 

public class Zebra : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

Các ZooCage sẽ tổ chức động vật của cùng một loại

public class ZooCage<T> where T : IZooAnimal 
{ 
    public IList<T> Animals { get; set; } 
} 

Lớp thú có lồng

public class Zoo 
{ 
    public IList<ZooCage<IZooAnimal>> ZooCages { get; set; } 
} 

Chương trình có sử dụng các lớp

class Program 
{ 
    static void Main(string[] args) 
    { 
     var lion = new Lion(); 
     var lionCage = new ZooCage<Lion>(); 
     lionCage.Animals = new List<Lion>(); 
     lionCage.Animals.Add(lion); 

     var zebra = new Zebra(); 
     var zebraCage = new ZooCage<Zebra>(); 
     zebraCage.Animals = new List<Zebra>(); 
     zebraCage.Animals.Add(zebra); 

     var zoo = new Zoo(); 
     zoo.ZooCages = new List<ZooCage<IZooAnimal>>(); 

     zoo.ZooCages.Add(lionCage); 
    } 
} 

Khi tôi biên dịch tôi nhận được follo lỗi cánh: Lỗi 2 Đối số 1: không thể chuyển đổi từ 'ConsoleApplication2.ZooCage<ConsoleApplication2.Lion>' thành 'ConsoleApplication2.ZooCage<ConsoleApplication2.IZooAnimal>'

Tôi phải làm gì để làm cho chương trình của mình chạy?

+2

Đọc về hiệp phương sai và contravariance ... có thể bắt đầu [ở đây] (http://stackoverflow.com/q/2033912/644812)? –

Trả lời

3

Bạn nên xác định danh sách của bạn không hợp với loại bê tông mà thực hiện giao diện, nhưng với giao diện:

var lionCage = new ZooCage<IZooAnimal>(); 
    lionCage.Animals = new List<IZooAnimal>(); 

Sau đó, mã của bạn sẽ làm việc như mong đợi.

Mã ban đầu không hoạt động, bởi vì nó không được phép chuyển đổi loại bê tông thành loại tổng quát (như @ default.kramer chỉ ra covariance and contravariance).

Các giải pháp mà tôi đã đưa ra được sau:

// your ZooCage is still generic 
public class ZooCage<T> 
{ 
    // but you declare on creation which type you want to contain only! 
    private Type cageType = null; 
    public ZooCage(Type iMayContain) 
    { 
     cageType = iMayContain; 
     animals = new List<T>(); 
    } 
    // check on add if the types are compatible 
    public void Add(T animal) 
    { 
     if (animal.GetType() != cageType) 
     { 
      throw new Exception("Sorry - no matching types! I may contain only " + cageType.ToString()); 
     } 
     animals.Add(animal); 
    } 
    // should be generic but not visible to outher world! 
    private IList<T> animals { get; set; } 
} 

Mã này cho phép bạn làm:

var lion = new Lion(); 
    var lionCage = new ZooCage<IZooAnimal>(typeof(Lion)); 
    lionCage.Add(lion); 

    var zebra = new Zebra(); 
    var zebraCage = new ZooCage<IZooAnimal>(typeof(Zebra)); 
    zebraCage.Add(zebra); 

Nhưng nó sẽ ném ra một lỗi trên:

zebraCage.Add(lion); 

Bây giờ vườn thú có thể được mở rộng một cách an toàn.

+0

+1: Yup, đó chắc chắn là vấn đề khớp chữ ký. – code4life

+2

Điều này sẽ ngăn chặn lỗi biên dịch, nhưng nó đánh bại toàn bộ mục đích của việc sử dụng Generics. Có thể cũng không làm cho ZooCage chung ở tất cả nếu bạn đang đi để làm điều này. Nó cũng không đáp ứng yêu cầu "ZooCage sẽ giữ động vật cùng loại", bởi vì tất cả các lồng sẽ cho phép động vật thuộc bất kỳ loại nào. – JLRishe

+0

Vấn đề là những gì OP muốn là không thể, không chỉ vì trình biên dịch nói như vậy, mà vì việc xác minh an toàn kiểu thời gian biên dịch sẽ đi ra ngoài cửa sổ. Trình biên dịch sẽ không thể xác minh rằng mã này là an toàn nếu bạn cho phép cú pháp đó, vì vậy bạn sẽ quay lại kiểm tra thời gian chạy cho mọi thứ. Vì vậy, nó không quan trọng như thế nào bạn xúc xắc này, nó không thể vì nó không phải là một ý tưởng tốt. –

2

Vì bạn muốn có nhiều lồng, nhưng mỗi loại lồng chỉ có thể chứa một con vật, mô hình của bạn hơi bị tắt.

Tôi viết lại đoạn code như sau:

  • IZooAnimal là không thay đổi.
  • Có giao diện tương đối ICage chấp nhận bất kỳ loại IZooAnimal nào. Điều đó cho phép bạn có một cái lồng được đánh mạnh mẽ cho mọi loại động vật.
  • Sau đó, tôi có Cage triển khai cụ thể ICage. Cage là chung chung, nhưng bạn có thể dễ dàng biến nó thành một lớp trừu tượng và sau đó thực hiện các lồng cụ thể cho động vật. Ví dụ, nếu ngựa vằn của bạn cần được cho ăn cỏ, và sư tử của bạn cần được cho ăn thịt, bạn có thể chuyên triển khai lồng của chúng.

Dưới đây là đoạn code hoàn chỉnh:

public interface IZooAnimal 
{ 
    string Id { get; set; } 
} 

public interface ICage<out T> where T : IZooAnimal 
{ 
    IReadOnlyCollection<T> Animals { get; } 
} 

public class Cage<T> : ICage<T> where T: IZooAnimal 
{ 
    private readonly List<T> animals = new List<T>(); 

    public IReadOnlyCollection<T> Animals 
    { 
     get 
     { 
      return animals.AsReadOnly(); 
     } 
    } 

    public void CageAnimal(T animal) 
    { 
     animals.Add(animal); 
    } 
} 

public class Lion : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

public class Zebra : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

public class Zoo 
{ 
    public IList<ICage<IZooAnimal>> Cages { get; set; } 
} 

internal class Program 
{ 

    private static void Main(string[] args) 
    { 
     var lion = new Lion(); 
     var zebra = new Zebra(); 
     var lionCage = new Cage<Lion>(); 
     lionCage.CageAnimal(lion); 

     var zebraCage = new Cage<Zebra>(); 
     zebraCage.CageAnimal(zebra); 

     var zoo = new Zoo(); 
     zoo.Cages.Add(lionCage); 
     zoo.Cages.Add(zebraCage); 

    } 
} 
3

@ câu trả lời DanielMann là khá tốt, nhưng bị một nhược điểm: giao diện ban đầu IList không thể được sử dụng với giao diện ICage. Thay vào đó, ICage phải trưng ra một ReadOnlyCollection, và đưa ra một phương thức mới gọi là CageAnimal.

Tôi cũng đã viết lại mã bằng cách sử dụng phương pháp tương tự. Việc triển khai ICage của tôi yếu hơn nhiều, nhưng nó cho phép bạn gắn bó với ngữ nghĩa IList bên trong.

public interface IZooAnimal 
{ 
    string Id { get; set; } 
} 

public class Lion : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

public class Zebra : IZooAnimal 
{ 
    public string Id { get; set; } 
} 

public interface ICage 
{ 
    IEnumerable<IZooAnimal> WeaklyTypedAnimals { get; } 
} 

public class Cage<T> : ICage where T : IZooAnimal 
{ 
    public IList<T> Animals { get; set; } 

    public IEnumerable<IZooAnimal> WeaklyTypedAnimals 
    { 
     get { return (IEnumerable<IZooAnimal>) Animals; } 
    } 
} 

public class Zoo 
{ 
    public IList<ICage> ZooCages { get; set; } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var lion = new Lion(); 
     var lionCage = new Cage<Lion>(); 
     lionCage.Animals = new List<Lion>(); 
     lionCage.Animals.Add(lion); 

     var zebra = new Zebra(); 
     var zebraCage = new Cage<Zebra>(); 
     zebraCage.Animals = new List<Zebra>(); 
     zebraCage.Animals.Add(zebra); 

     var zoo = new Zoo(); 
     zoo.ZooCages = new List<ICage>(); 

     zoo.ZooCages.Add(lionCage); 
    } 
} 
+0

Điểm tuyệt vời. Tôi đã xem xét cách tiếp cận của bạn! :) –

+0

Chỉ có rất nhiều cách để làm sư tử da. :) – CSJ

+0

Thật vậy - nhiều giải pháp thanh lịch và sạch hơn tôi! :) – pasty

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