Ví dụ hiệp phương sai và chống chỉ định


162

Tôi có một chút khó khăn để hiểu làm thế nào tôi sẽ sử dụng hiệp phương sai và chống chỉ định trong thế giới thực.

Cho đến nay, các ví dụ duy nhất tôi thấy là cùng một ví dụ mảng cũ.

object[] objectArray = new string[] { "string 1", "string 2" };

Thật tuyệt khi thấy một ví dụ cho phép tôi sử dụng nó trong quá trình phát triển nếu tôi có thể thấy nó được sử dụng ở nơi khác.


1
Tôi khám phá hiệp phương sai trong câu trả lời này cho câu hỏi (của riêng tôi): các kiểu hiệp phương sai: ví dụ . Tôi nghĩ rằng bạn sẽ thấy nó thú vị, và hy vọng mang tính hướng dẫn.
Cristian Diaconescu

Câu trả lời:


109

Giả sử bạn có một Lớp người và một lớp xuất phát từ đó, Sư phụ. Bạn có một số hoạt động lấy một IEnumerable<Person>đối số. Trong lớp học của bạn, bạn có một phương thức trả về một IEnumerable<Teacher>. Hiệp phương sai cho phép bạn trực tiếp sử dụng kết quả đó cho các phương thức thực hiện IEnumerable<Person>, thay thế một loại có nguồn gốc nhiều hơn cho loại ít dẫn xuất hơn (chung chung hơn). Chống chỉ định, phản trực giác, cho phép bạn sử dụng một loại chung chung hơn, trong đó một loại dẫn xuất hơn được chỉ định.

Xem thêm Hiệp phương sai và Chống chỉ định trong Generics trên MSDN .

Các lớp học :

public class Person 
{
     public string Name { get; set; }
} 

public class Teacher : Person { } 

public class MailingList
{
    public void Add(IEnumerable<out Person> people) { ... }
}

public class School
{
    public IEnumerable<Teacher> GetTeachers() { ... }
}

public class PersonNameComparer : IComparer<Person>
{
    public int Compare(Person a, Person b) 
    { 
        if (a == null) return b == null ? 0 : -1;
        return b == null ? 1 : Compare(a,b);
    }

    private int Compare(string a, string b)
    {
        if (a == null) return b == null ? 0 : -1;
        return b == null ? 1 : a.CompareTo(b);
    }
}

Cách sử dụng :

var teachers = school.GetTeachers();
var mailingList = new MailingList();

// Add() is covariant, we can use a more derived type
mailingList.Add(teachers);

// the Set<T> constructor uses a contravariant interface, IComparer<in T>,
// we can use a more generic type than required.
// See https://msdn.microsoft.com/en-us/library/8ehhxeaf.aspx for declaration syntax
var teacherSet = new SortedSet<Teachers>(teachers, new PersonNameComparer());

14
@FilipBartuzi - nếu, giống như tôi khi tôi viết câu trả lời này, bạn đã được tuyển dụng tại một trường đại học rất giống một ví dụ trong thế giới thực.
tvanfosson

5
Làm thế nào điều này có thể được đánh dấu câu trả lời khi nó không trả lời câu hỏi và không đưa ra bất kỳ ví dụ nào về việc sử dụng phương sai co / contra trong c #?
barakcaf

@barakcaf đã thêm một ví dụ về chống chỉ định. không chắc chắn tại sao bạn không thấy ví dụ về hiệp phương sai - có lẽ bạn cần phải cuộn mã xuống - nhưng tôi đã thêm một số nhận xét xung quanh đó.
tvanfosson

@tvanfosson mã sử dụng co / contra, tôi nghĩ rằng nó không hiển thị cách khai báo. Ví dụ này không hiển thị việc sử dụng vào / ra trong khai báo chung trong khi câu trả lời khác thì không.
barakcaf

Vì vậy, nếu tôi hiểu đúng, hiệp phương sai là thứ cho phép nguyên tắc thay thế của Liskov trong C #, có đúng không?
Miguel Veloso

136
// Contravariance
interface IGobbler<in T> {
    void gobble(T t);
}

// Since a QuadrupedGobbler can gobble any four-footed
// creature, it is OK to treat it as a donkey gobbler.
IGobbler<Donkey> dg = new QuadrupedGobbler();
dg.gobble(MyDonkey());

// Covariance
interface ISpewer<out T> {
    T spew();
}

// A MouseSpewer obviously spews rodents (all mice are
// rodents), so we can treat it as a rodent spewer.
ISpewer<Rodent> rs = new MouseSpewer();
Rodent r = rs.spew();

Để hoàn thiện

// Invariance
interface IHat<T> {
    void hide(T t);
    T pull();
}

// A RabbitHat…
IHat<Rabbit> rHat = RabbitHat();

// …cannot be treated covariantly as a mammal hat…
IHat<Mammal> mHat = rHat;      // Compiler error
// …because…
mHat.hide(new Dolphin());      // Hide a dolphin in a rabbit hat??

// It also cannot be treated contravariantly as a cottontail hat…
IHat<CottonTail> cHat = rHat;  // Compiler error
// …because…
rHat.hide(new MarshRabbit());
cHat.pull();                   // Pull a marsh rabbit out of a cottontail hat??

138
Tôi thích ví dụ thực tế này. Tôi vừa mới viết một số mã lừa đảo vào tuần trước và tôi rất vui vì bây giờ chúng tôi đã hiệp phương sai. :-)
Eric Lippert

4
Nhận xét này ở trên với @javadba nói với THE EricLippert thế nào là hiệp phương sai và chống chỉ định là một ví dụ hiệp phương thực tế về việc tôi nói với bà tôi cách hút trứng! : p
iAteABug_And_iLiked_it

1
Câu hỏi không hỏi những gì chống chỉ định và hiệp phương sai có thể làm gì , nó hỏi tại sao bạn cần sử dụng nó . Ví dụ của bạn là xa thực tế bởi vì nó không yêu cầu. Tôi có thể tạo QuadrupedGobbler và coi nó như chính nó (gán nó cho IGobbler <Quadruped>) và nó vẫn có thể ngấu nghiến Donkey (Tôi có thể truyền Donkey vào phương thức Gobble yêu cầu Quadruped). Không có chống chỉ định cần thiết. Thật tuyệt khi chúng ta có thể coi QuadrupedGobbler là DonkeyGobbler, nhưng tại sao trong trường hợp này, nếu QuadrupedGobbler có thể ngấu nghiến Donkey?
Wired_in

1
@wired_in Bởi vì khi bạn chỉ quan tâm đến những con lừa, việc trở nên chung chung hơn có thể cản trở bạn. Ví dụ: nếu bạn có một trang trại cung cấp lừa để ngấu nghiến, bạn có thể diễn đạt điều này như void feed(IGobbler<Donkey> dg). Thay vào đó, nếu bạn lấy một IGobbler <Quadruped> làm tham số, bạn không thể vượt qua một con rồng chỉ ăn lừa.
Marcelo Cantos

1
Chờ đến bữa tiệc muộn, nhưng đây là ví dụ bằng văn bản hay nhất tôi từng thấy xung quanh SO. Làm cho ý nghĩa hoàn toàn trong khi vô lý. Tôi sẽ phải nâng cấp trò chơi của mình bằng câu trả lời ...
Jesse Williams

120

Đây là những gì tôi kết hợp để giúp tôi hiểu sự khác biệt

public interface ICovariant<out T> { }
public interface IContravariant<in T> { }

public class Covariant<T> : ICovariant<T> { }
public class Contravariant<T> : IContravariant<T> { }

public class Fruit { }
public class Apple : Fruit { }

public class TheInsAndOuts
{
    public void Covariance()
    {
        ICovariant<Fruit> fruit = new Covariant<Fruit>();
        ICovariant<Apple> apple = new Covariant<Apple>();

        Covariant(fruit);
        Covariant(apple); //apple is being upcasted to fruit, without the out keyword this will not compile
    }

    public void Contravariance()
    {
        IContravariant<Fruit> fruit = new Contravariant<Fruit>();
        IContravariant<Apple> apple = new Contravariant<Apple>();

        Contravariant(fruit); //fruit is being downcasted to apple, without the in keyword this will not compile
        Contravariant(apple);
    }

    public void Covariant(ICovariant<Fruit> fruit) { }

    public void Contravariant(IContravariant<Apple> apple) { }
}

tldr

ICovariant<Fruit> apple = new Covariant<Apple>(); //because it's covariant
IContravariant<Apple> fruit = new Contravariant<Fruit>(); //because it's contravariant

10
Đây là điều tốt nhất tôi đã thấy cho đến nay là rõ ràng và súc tích. Ví dụ tuyệt vời!
Rob L

6
Làm thế nào trái cây có thể bị hạ thấp thành táo (trong Contravarianceví dụ) khi Fruitlà cha mẹ của Apple?
Tobias Marschall

@TobiasMarschall có nghĩa là bạn phải nghiên cứu thêm về "đa hình"
snr

56

Các từ khóa vào và ra kiểm soát các quy tắc truyền của trình biên dịch cho các giao diện và đại biểu với các tham số chung:

interface IInvariant<T> {
    // This interface can not be implicitly cast AT ALL
    // Used for non-readonly collections
    IList<T> GetList { get; }
    // Used when T is used as both argument *and* return type
    T Method(T argument);
}//interface

interface ICovariant<out T> {
    // This interface can be implicitly cast to LESS DERIVED (upcasting)
    // Used for readonly collections
    IEnumerable<T> GetList { get; }
    // Used when T is used as return type
    T Method();
}//interface

interface IContravariant<in T> {
    // This interface can be implicitly cast to MORE DERIVED (downcasting)
    // Usually means T is used as argument
    void Method(T argument);
}//interface

class Casting {

    IInvariant<Animal> invariantAnimal;
    ICovariant<Animal> covariantAnimal;
    IContravariant<Animal> contravariantAnimal;

    IInvariant<Fish> invariantFish;
    ICovariant<Fish> covariantFish;
    IContravariant<Fish> contravariantFish;

    public void Go() {

        // NOT ALLOWED invariants do *not* allow implicit casting:
        invariantAnimal = invariantFish; 
        invariantFish = invariantAnimal; // NOT ALLOWED

        // ALLOWED covariants *allow* implicit upcasting:
        covariantAnimal = covariantFish; 
        // NOT ALLOWED covariants do *not* allow implicit downcasting:
        covariantFish = covariantAnimal; 

        // NOT ALLOWED contravariants do *not* allow implicit upcasting:
        contravariantAnimal = contravariantFish; 
        // ALLOWED contravariants *allow* implicit downcasting
        contravariantFish = contravariantAnimal; 

    }//method

}//class

// .NET Framework Examples:
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable { }
public interface IEnumerable<out T> : IEnumerable { }


class Delegates {

    // When T is used as both "in" (argument) and "out" (return value)
    delegate T Invariant<T>(T argument);

    // When T is used as "out" (return value) only
    delegate T Covariant<out T>();

    // When T is used as "in" (argument) only
    delegate void Contravariant<in T>(T argument);

    // Confusing
    delegate T CovariantBoth<out T>(T argument);

    // Confusing
    delegate T ContravariantBoth<in T>(T argument);

    // From .NET Framework:
    public delegate void Action<in T>(T obj);
    public delegate TResult Func<in T, out TResult>(T arg);

}//class

Giả sử Cá là một kiểu con của Động vật. Bằng cách trả lời tuyệt vời.
Rajan Prasad

48

Đây là một ví dụ đơn giản sử dụng hệ thống phân cấp thừa kế.

Đưa ra hệ thống phân cấp lớp đơn giản:

nhập mô tả hình ảnh ở đây

Và trong mã:

public abstract class LifeForm  { }
public abstract class Animal : LifeForm { }
public class Giraffe : Animal { }
public class Zebra : Animal { }

Bất biến (tức là các tham số loại chung * không * được trang trí bằng inhoặc outtừ khóa)

Dường như, một phương pháp như thế này

public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
    foreach (var lifeForm in lifeForms)
    {
        Console.WriteLine(lifeForm.GetType().ToString());
    }
}

... nên chấp nhận một bộ sưu tập không đồng nhất: (nó thực hiện)

var myAnimals = new List<LifeForm>
{
    new Giraffe(),
    new Zebra()
};
PrintLifeForms(myAnimals); // Giraffe, Zebra

Tuy nhiên, vượt qua một bộ sưu tập của một loại dẫn xuất nhiều hơn thất bại!

var myGiraffes = new List<Giraffe>
{
    new Giraffe(), // "Jerry"
    new Giraffe() // "Melman"
};
PrintLifeForms(myGiraffes); // Compile Error!

cannot convert from 'System.Collections.Generic.List<Giraffe>' to 'System.Collections.Generic.IList<LifeForm>'

Tại sao? Bởi vì tham số chung IList<LifeForm>không phải là covariant - IList<T>là bất biến, nên IList<LifeForm>chỉ chấp nhận các bộ sưu tập (thực hiện IList) trong đó loại tham số Tphải là LifeForm.

Nếu việc triển khai phương thức PrintLifeFormslà độc hại (nhưng có cùng chữ ký phương thức), lý do tại sao trình biên dịch ngăn chặn vượt qua List<Giraffe>trở nên rõ ràng:

 public static void PrintLifeForms(IList<LifeForm> lifeForms)
 {
     lifeForms.Add(new Zebra());
 }

IListcho phép thêm hoặc loại bỏ các phần tử, LifeFormdo đó , bất kỳ lớp con nào có thể được thêm vào tham số lifeFormsvà sẽ vi phạm loại của bất kỳ tập hợp các loại dẫn xuất nào được truyền cho phương thức. (Ở đây, phương pháp độc hại sẽ cố gắng thêm một Zebrađến var myGiraffes). May mắn thay, trình biên dịch bảo vệ chúng ta khỏi nguy hiểm này.

Hiệp phương sai (Chung với loại tham số trang trí với out)

Hiệp phương sai được sử dụng rộng rãi với các bộ sưu tập bất biến (tức là nơi các yếu tố mới không thể được thêm hoặc xóa khỏi bộ sưu tập)

Giải pháp cho ví dụ trên là để đảm bảo rằng loại bộ sưu tập chung covariant được sử dụng, ví dụ IEnumerable(được định nghĩa là IEnumerable<out T>). IEnumerablekhông có phương thức nào để thay đổi bộ sưu tập và do kết quả của outhiệp phương sai, mọi bộ sưu tập có kiểu con LifeFormbây giờ có thể được chuyển sang phương thức:

public static void PrintLifeForms(IEnumerable<LifeForm> lifeForms)
{
    foreach (var lifeForm in lifeForms)
    {
        Console.WriteLine(lifeForm.GetType().ToString());
    }
}

PrintLifeFormsbây giờ có thể được gọi với Zebras, Giraffesvà bất kỳ IEnumerable<>của bất kỳ lớp con củaLifeForm

Chống chỉ định (Chung với loại tham số được trang trí bằng in)

Chống chỉ định thường được sử dụng khi các hàm được truyền dưới dạng tham số.

Đây là một ví dụ về hàm, lấy Action<Zebra>tham số làm tham số và gọi nó trên một thể hiện đã biết của Zebra:

public void PerformZebraAction(Action<Zebra> zebraAction)
{
    var zebra = new Zebra();
    zebraAction(zebra);
}

Như mong đợi, điều này hoạt động tốt:

var myAction = new Action<Zebra>(z => Console.WriteLine("I'm a zebra"));
PerformZebraAction(myAction); // I'm a zebra

Theo trực giác, điều này sẽ thất bại:

var myAction = new Action<Giraffe>(g => Console.WriteLine("I'm a giraffe"));
PerformZebraAction(myAction); 

cannot convert from 'System.Action<Giraffe>' to 'System.Action<Zebra>'

Tuy nhiên, điều này thành công

var myAction = new Action<Animal>(a => Console.WriteLine("I'm an animal"));
PerformZebraAction(myAction); // I'm an animal

và thậm chí điều này cũng thành công:

var myAction = new Action<object>(a => Console.WriteLine("I'm an amoeba"));
PerformZebraAction(myAction); // I'm an amoeba

Tại sao? Bởi vì Actionđược định nghĩa là Action<in T>, nghĩa là nó contravariantcó nghĩa là Action<Zebra> myAction, myActioncó thể ở mức "nhất" a Action<Zebra>, nhưng các siêu lớp có nguồn gốc ít hơn Zebracũng được chấp nhận.

Mặc dù lúc đầu điều này có thể không trực quan (ví dụ: làm thế nào có Action<object>thể truyền như một tham số yêu cầu Action<Zebra>?), Nếu bạn giải nén các bước, bạn sẽ lưu ý rằng chính hàm được gọi ( PerformZebraAction) chịu trách nhiệm truyền dữ liệu (trong trường hợp này là một Zebratrường hợp ) cho chức năng - dữ liệu không đến từ mã gọi.

Do cách tiếp cận ngược của việc sử dụng các hàm bậc cao hơn theo cách này, vào thời điểm Actionđược gọi, nó là Zebratrường hợp dẫn xuất nhiều hơn được gọi với zebraActionhàm (được truyền dưới dạng tham số), mặc dù chính hàm đó sử dụng loại ít dẫn xuất hơn.


7
Đây là một lời giải thích tuyệt vời cho các tùy chọn phương sai khác nhau, vì nó nói qua ví dụ và cũng làm rõ lý do tại sao trình biên dịch hạn chế hoặc cho phép mà không có từ khóa vào / ra
Vikhram 13/11/18

Ở đâu là intừ khóa được sử dụng để contravariance ?
javadba

@javadba ở trên, Action<in T>và chống Func<in T, out TResult>chỉ định trong kiểu đầu vào. (Ví dụ của tôi sử dụng các loại bất biến (Danh sách), covariant (IEnumerable) và contravariant (Action, Func))
StuartLC

Ok tôi không làm C#như vậy sẽ không biết điều đó.
javadba

Nó khá giống nhau trong Scala, chỉ khác cú pháp - [+ T] sẽ là biến đổi trong T, [-T] sẽ bị chống lại trong T, Scala cũng có thể thực thi ràng buộc 'giữa' và lớp con bừa bãi 'Không có gì', mà C # không có.
StuartLC

32
class A {}
class B : A {}

public void SomeFunction()
{
    var someListOfB = new List<B>();
    someListOfB.Add(new B());
    someListOfB.Add(new B());
    someListOfB.Add(new B());
    SomeFunctionThatTakesA(someListOfB);
}

public void SomeFunctionThatTakesA(IEnumerable<A> input)
{
    // Before C# 4, you couldn't pass in List<B>:
    // cannot convert from
    // 'System.Collections.Generic.List<ConsoleApplication1.B>' to
    // 'System.Collections.Generic.IEnumerable<ConsoleApplication1.A>'
}

Về cơ bản bất cứ khi nào bạn có một hàm có Vô số một loại, bạn không thể chuyển vào Vô số loại có nguồn gốc mà không sử dụng nó một cách rõ ràng.

Chỉ để cảnh báo bạn về một cái bẫy mặc dù:

var ListOfB = new List<B>();
if(ListOfB is IEnumerable<A>)
{
    // In C# 4, this branch will
    // execute...
    Console.Write("It is A");
}
else if (ListOfB is IEnumerable<B>)
{
    // ...but in C# 3 and earlier,
    // this one will execute instead.
    Console.Write("It is B");
}

Dù sao đó cũng là mã khủng khiếp, nhưng nó tồn tại và hành vi thay đổi trong C # 4 có thể đưa ra các lỗi tinh vi và khó tìm nếu bạn sử dụng cấu trúc như thế này.


Vì vậy, điều này ảnh hưởng đến các bộ sưu tập nhiều hơn bất cứ điều gì, bởi vì trong c # 3, bạn có thể chuyển một loại dẫn xuất nhiều hơn vào một phương thức của loại ít dẫn xuất hơn.
Dao cạo

3
Vâng, thay đổi lớn là IEnumerable hiện hỗ trợ điều này, trong khi trước đây thì không.
Michael Stum

4

Từ MSDN

Ví dụ mã sau đây cho thấy hỗ trợ hiệp phương sai và chống đối với các nhóm phương thức

static object GetObject() { return null; }
static void SetObject(object obj) { }

static string GetString() { return ""; }
static void SetString(string str) { }

static void Test()
{
    // Covariance. A delegate specifies a return type as object, 
    // but you can assign a method that returns a string.
    Func<object> del = GetString;

    // Contravariance. A delegate specifies a parameter type as string, 
    // but you can assign a method that takes an object.
    Action<string> del2 = SetObject;
}

4

Chống chỉ định

Trong thế giới thực, bạn luôn có thể sử dụng nơi trú ẩn cho động vật thay vì nơi trú ẩn cho thỏ vì mỗi khi một nơi trú ẩn động vật lại nuôi một con thỏ thì đó là một con vật. Tuy nhiên, nếu bạn sử dụng nơi trú ẩn của thỏ thay vì nơi trú ẩn của động vật, nhân viên của nó có thể bị hổ ăn thịt.

Trong mã, phương tiện này là nếu bạn có một IShelter<Animal> animalsbạn có thể chỉ đơn giản là viết IShelter<Rabbit> rabbits = animals nếu bạn hứa và sử dụng Ttrong IShelter<T>chỉ như là các thông số phương pháp thích như vậy:

public class Contravariance
{
    public class Animal { }
    public class Rabbit : Animal { }

    public interface IShelter<in T>
    {
        void Host(T thing);
    }

    public void NoCompileErrors()
    {
        IShelter<Animal> animals = null;
        IShelter<Rabbit> rabbits = null;

        rabbits = animals;
    }
}

và thay thế một mục bằng một chung chung hơn, tức là giảm phương sai hoặc giới thiệu Contra sai.

Hiệp phương sai

Trong thế giới thực, bạn luôn có thể sử dụng một nhà cung cấp thỏ thay vì một nhà cung cấp động vật bởi vì mỗi khi một nhà cung cấp thỏ cho bạn một con thỏ thì đó là một con vật. Tuy nhiên, nếu bạn sử dụng một nhà cung cấp động vật thay vì một nhà cung cấp thỏ, bạn có thể bị hổ ăn thịt.

Trong mã, điều này có nghĩa là nếu bạn có một lệnh, ISupply<Rabbit> rabbitsbạn có thể viết đơn giản ISupply<Animal> animals = rabbits nếu bạn hứa và chỉ sử dụng Tcác ISupply<T>giá trị trả về như phương thức:

public class Covariance
{
    public class Animal { }
    public class Rabbit : Animal { }

    public interface ISupply<out T>
    {
        T Get();
    }

    public void NoCompileErrors()
    {
        ISupply<Animal> animals = null;
        ISupply<Rabbit> rabbits = null;

        animals = rabbits;
    }
}

và thay thế một mục bằng một mục có nguồn gốc nhiều hơn, tức là tăng phương sai hoặc giới thiệu phương sai đồng .

Nói chung, đây chỉ là một lời hứa có thể kiểm tra được trong thời gian biên dịch từ bạn rằng bạn sẽ đối xử với một loại chung theo một kiểu nhất định để giữ an toàn cho loại đó và không khiến ai ăn.

Bạn có thể muốn đọc cái này để quấn đôi đầu của bạn xung quanh cái này.


bạn có thể bị ăn thịt bởi một con hổ Đáng để nâng cấp
javadba

Nhận xét của bạn trên contravariancelà thú vị. Tôi đang đọc về nó như chỉ ra một yêu cầu hoạt động : rằng loại tổng quát hơn phải hỗ trợ các trường hợp sử dụng của tất cả các loại có nguồn gốc từ nó. Vì vậy, trong trường hợp này, nơi trú ẩn động vật phải có khả năng hỗ trợ che chở cho mọi loại động vật. Trong trường hợp đó, việc thêm một lớp con mới có thể phá vỡ lớp cha! Đó là - nếu chúng ta thêm một kiểu con Tyrannosaurus Rex thì nó có thể phá hủy nơi trú ẩn động vật hiện tại của chúng ta .
javadba

(Tiếp theo). Điều đó khác biệt mạnh mẽ với hiệp phương sai được mô tả rõ ràng về cấu trúc : tất cả các kiểu con cụ thể hơn đều hỗ trợ các hoạt động được xác định trong siêu kiểu - nhưng không nhất thiết phải theo cách tương tự.
javadba

3

Đại biểu chuyển đổi giúp tôi hình dung cả hai khái niệm làm việc cùng nhau:

delegate TOutput Converter<in TInput, out TOutput>(TInput input);

TOutputđại diện cho hiệp phương sai trong đó một phương thức trả về một kiểu cụ thể hơn .

TInputđại diện contravariance nơi một phương pháp được thông qua một loại ít cụ thể hơn .

public class Dog { public string Name { get; set; } }
public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } }

public static Poodle ConvertDogToPoodle(Dog dog)
{
    return new Poodle() { Name = dog.Name };
}

List<Dog> dogs = new List<Dog>() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } };
List<Poodle> poodles = dogs.ConvertAll(new Converter<Dog, Poodle>(ConvertDogToPoodle));
poodles[0].DoBackflip();
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.