2016-05-31 12 views
0

ich eine harte Zeit zu verstehen, warum Action<T> kontra ist und Func<T> covariant, warum Action<T> kontra sein sollte und warum sollte Func<T> covariant sein, irgendwelche Richtlinien darüber, wann man verwenden und wenn die anderen zu verwenden.Delegierten Varianz

+0

Aktion ist für ungültige Funktionen. Func ist für zurückkehrende Funktionen –

+4

Verstehst du, womit Kovarianz und Contravariance anfangen? Es ist nicht klar, welches Teil des Puzzles du gerade vermisst. –

+0

Ich verstehe die Verwendung der beiden, aber ich verstehe nicht, warum Contravariance in Action , warum die Kontravarianz die Subtypisierung in T umkehrt. Warum ? –

Antwort

2

Wenn Action<T> kovariant waren Sie wäre in der Lage, dies zu tun:

Action<string> sa = s => { Console.WriteLine(sa[0]); } 
Action<object> oa = sa; 
oa(1); 

und eine int zu einer Aktion mit einem String-Parameter übergeben, die nicht sicher ist. Es ist sicher, in die andere Richtung zu gehen und den Parametertyp z.

Action<object> oa = o => { Console.WriteLine(o.GetHashCode()); } 
Action<string> sa = oa; 
sa("test"); 

da jede string ist auch ein object.

+0

also geht es um Typ Sicherheit? Keine anderen Gründe? –

+1

@ZakaryaIsser - Varianz wurde den Delegierten- und Schnittstellenparametern hinzugefügt, damit sichere Zuordnungen im Typsystem ausgedrückt werden können. In C# 3 und früher waren Sie nicht in der Lage, diese Zuweisungen vorzunehmen, sodass die Varianz die Flexibilität des Typsystems erhöhte, ohne die Sicherheit zu beeinträchtigen. Ich bezweifle, dass C# Systemmerkmale hinzufügen wird, die nicht gut sind. – Lee

+0

Vielen Dank Herr das ist eine sehr klare Consise Erklärung, Sie ansered das warum :) –

1

Ein Action<T> nimmt eine T als Eingabe und gibt nichts zurück, so dass es kontravariant sein kann.

Ein Func<T> nimmt keine Eingabe und gibt T zurück, so dass es kovariant sein kann.

Sie dienen verschiedenen Zwecken und sind nicht austauschbar.

Im Allgemeinen kann eine Schnittstelle kovarianten sein, wenn es nur die generischen Parameter in Ausgänge verwendet (z.B. Methoden, die einen Rück T nehmen aber nicht T als Eingabe schreibgeschützte Eigenschaften).

Das klassische Beispiel ist IEnumerable<T>. es gibt nur T s zurück - es hat keine Methoden oder Eigenschaften, die einen T einen Eingang nehmen.

kann eine Schnittstelle kontra sein, wenn es nur die generischen Parameter verwendet (s) nur als Eingänge

Ein Beispiel hierfür IComparer<T> ist. Es dauert zwei T s und bestimmt, ob sie gleich sind (oder wenn man „größer als“ die andere "). Es hat keine Rückgabewerte, die auf T basieren.

wann soll ich meine benutzerdefiniert Delegierten machen covariant und wenn sie kontra machen?

Ein Delegierter, wenn es nur kehrtT s kovariant werden kann. es kontra werden kann, wenn in nur Eingänge, die T s.

+0

danke für die schnelle Antwort, also wann sollte ich meine benutzerdefinierten Delegierten Covariant und wenn sie kontraviant machen? –

0

Der Unterschied zwischen Func und Action besteht einfach darin, ob der Delegat einen Wert zurückgeben soll (Func verwenden) oder nicht (Aktion verwenden). Func ist wahrscheinlich die am häufigsten in LINQ verwendet - zum Beispiel in Projektionen:

list.Select(x => x.SomeProperty)

oder Filterung:

list.Where(x => x.SomeValue == someOtherValue)

oder Schlüssel Auswahl:

list.Join(otherList, x => x.FirstKey, y => y.SecondKey, ...)

Aktion wird häufiger für Dinge wie verwendet e : Führt die angegebene Aktion für jedes Element in der Liste aus. Ich benutze das weniger oft als Func, obwohl ich manchmal die parameterlose Version für Dinge wie Control.BeginInvoke und Dispatcher.BeginInvoke verwende.

Prädikat ist nur eine spezielle verkleinerte Func wirklich, vorgestellt, bevor alle Func und die meisten der Action-Delegierten kamen. Ich vermute, wenn wir bereits Func und Action in ihren verschiedenen Erscheinungsformen gehabt hätten, wäre Predicate nicht eingeführt worden ... obwohl es dem Einsatz des Delegierten eine gewisse Bedeutung verleiht, während Func und Action für sehr unterschiedliche Zwecke verwendet werden Zwecke.

Prädikat ist vor allem in List<T> für Methoden wie FindAll und RemoveAll

1

Hier einige kommentierten Code ist, die auch dazu beitragen, könnten Sie im Detail zu verstehen.

Es verwendet eine Tier-/Katzen-/Hundeklassenherarchie, um zu illustrieren, warum Kontravarianz und Kovarianz so ist, wie es für Action<T> und Func<T> ist.

using System; 

namespace Demo 
{ 
    class Animal 
    { 
     public virtual void MakeNoise() {} 
    } 

    class Dog: Animal 
    { 
     public override void MakeNoise() 
     { 
      Bark(); 
     } 

     public void Bark() {} 
    } 

    class Cat : Animal 
    { 
     public override void MakeNoise() 
     { 
      Meow(); 
     } 

     public void Meow() {} 
    } 

    class Program 
    { 
     static void handleAnimal(Animal animal) // I can handle cats AND dogs. 
     { 
      animal.MakeNoise(); 
     } 

     static void handleCat(Cat cat) // I only handle cats. 
     { 
      cat.Meow(); 
     } 

     static Cat createCat() // I only create cats. 
     { 
      return new Cat(); 
     } 

     static Dog createDog() // I only create dogs. 
     { 
      return new Dog(); 
     } 

     static Animal createAnimal() // I only create animals. 
     { 
      return new Animal(); 
     } 

     public static void Main() 
     { 
      // Action<T> is contravariant. 

      // Since the parameter of handleAnimal() is of type Animal, 
      // it can handle both cats and dogs. Therefore Action<Cat> 
      // and Action<Dog> can both be assigned from it. 

      Action<Cat> catAction = handleAnimal; 
      Action<Dog> dogAction = handleAnimal; 

      catAction(new Cat()); // Cat passed to handleAnimal() - OK. 
      dogAction(new Dog()); // Dog passed to handleAnimal() - OK. 

      // Imagine that Action<T> was covariant. 
      // Then you would be able to do this: 

      Action<Animal> animalAction = handleCat; // This line won't compile, because: 
      animalAction(new Animal());    // Animal passed to handleCat() - NOT OK! 

      // Func<T> has a covariant return type. 

      // Since the type returned from Func<Animal> is of type Animal, 
      // any type derived from Animal will do. 
      // Therefore it can be assigned from either createCat() or createDog(). 

      Func<Animal> catFunc = createCat; 
      Func<Animal> dogFunc = createDog; 
      Func<Animal> animalFunc = createAnimal; 

      Animal animal1 = catFunc(); // Cat returned and assigned to Animal - OK. 
      Animal animal2 = dogFunc(); // Dog returned and assigned to Animal - OK. 
      Animal animal3 = animalFunc(); // Animal returned and assigned to Animal - OK. 

      // Imagine that Func<T> was contravariant. 
      // Then you would be able to do this: 

      Func<Cat> catMaker = createAnimal; // This line won't compile because: 
      Cat cat = catMaker();    // Animal would be assigned to Cat - NOT OK! 
     } 
    } 
} 
+0

Wow perfekt, das ist eine gute Probe mit den Kommentaren –