Sdílet prostřednictvím


Vytváření variant obecných rozhraní (C#)

Parametry obecného typu můžete deklarovat v rozhraních jako kovariantní nebo kontravariantní. Kovariance umožňuje metodám rozhraní mít více odvozených návratových typů, než které jsou definovány parametry obecného typu. Kontravariance umožňuje metodám rozhraní mít typy argumentů, které jsou méně odvozené, než které jsou určeny obecnými parametry. Obecné rozhraní, které má kovariantní nebo kontravariantní parametry obecného typu, se nazývá varianta.

Poznámka:

Rozhraní .NET Framework 4 zavedlo podporu odchylek pro několik existujících obecných rozhraní. Seznam variant rozhraní v rozhraní .NET najdete v tématu Variance v obecných rozhraních (C#).

Deklarace obecných rozhraní variant

Variantní obecná rozhraní můžete deklarovat pomocí inout klíčových slov pro parametry obecného typu.

Důležité

ref, ina out parametry v jazyce C# nemohou být variantou. Typy hodnot také nepodporují odchylku.

Kovariantní parametr obecného typu můžete deklarovat pomocí klíčového out slova. Kovariantní typ musí splňovat následující podmínky:

  • Typ se používá pouze jako návratový typ metod rozhraní a nepoužívá se jako typ argumentů metody. To je znázorněno v následujícím příkladu, ve kterém je typ R deklarován kovariant.

    interface ICovariant<out R>
    {
        R GetSomething();
        // The following statement generates a compiler error.
        // void SetSomething(R sampleArg);
    
    }
    

    Toto pravidlo má jednu výjimku. Pokud jako parametr metody máte kontravariantní obecný delegát, můžete tento typ použít jako parametr obecného typu pro delegáta. To je znázorněno typem R v následujícím příkladu. Další informace najdete v tématech Variance in Delegates (C#) a Using Variance for Func and Action Generic Delegates (C#) (Variance for Func and Action Generic Delegates).

    interface ICovariant<out R>
    {
        void DoSomething(Action<R> callback);
    }
    
  • Typ se nepoužívá jako obecné omezení pro metody rozhraní. To je znázorněno v následujícím kódu.

    interface ICovariant<out R>
    {
        // The following statement generates a compiler error
        // because you can use only contravariant or invariant types
        // in generic constraints.
        // void DoSomething<T>() where T : R;
    }
    

Parametr obecného typu můžete deklarovat kontravariant pomocí klíčového in slova. Kontravariantní typ lze použít pouze jako typ argumentů metody, a ne jako návratový typ metod rozhraní. Kontravariantní typ lze použít také pro obecná omezení. Následující kód ukazuje, jak deklarovat kontravariantní rozhraní a použít obecné omezení pro jednu z jeho metod.

interface IContravariant<in A>
{
    void SetSomething(A sampleArg);
    void DoSomething<T>() where T : A;
    // The following statement generates a compiler error.
    // A GetSomething();
}

Je také možné podporovat kovarianci i kontravarianci ve stejném rozhraní, ale pro různé parametry typu, jak je znázorněno v následujícím příkladu kódu.

interface IVariant<out R, in A>
{
    R GetSomething();
    void SetSomething(A sampleArg);
    R GetSetSomethings(A sampleArg);
}

Implementace variantních obecných rozhraní

Ve třídách implementujete variantní obecná rozhraní pomocí stejné syntaxe, která se používá pro invariantní rozhraní. Následující příklad kódu ukazuje, jak implementovat kovariantní rozhraní v obecné třídě.

interface ICovariant<out R>
{
    R GetSomething();
}
class SampleImplementation<R> : ICovariant<R>
{
    public R GetSomething()
    {
        // Some code.
        return default(R);
    }
}

Třídy, které implementují variantní rozhraní, jsou invariantní. Představte si například následující kód.

// The interface is covariant.
ICovariant<Button> ibutton = new SampleImplementation<Button>();
ICovariant<Object> iobj = ibutton;

// The class is invariant.
SampleImplementation<Button> button = new SampleImplementation<Button>();
// The following statement generates a compiler error
// because classes are invariant.
// SampleImplementation<Object> obj = button;

Rozšíření variantních obecných rozhraní

Když rozšíříte obecné rozhraní varianty, musíte použít in klíčová slova a out explicitně určit, zda odvozené rozhraní podporuje odchylku. Kompilátor neodvozuje odchylku od rozšířeného rozhraní. Představte si například následující rozhraní.

interface ICovariant<out T> { }
interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }

IInvariant<T> V rozhraní je parametr T obecného typu invariantní, zatímco parametr IExtCovariant<out T> typu je kovariantní, i když obě rozhraní rozšiřují stejné rozhraní. Stejné pravidlo se použije na parametry kontravariantního obecného typu.

Můžete vytvořit rozhraní, které rozšiřuje rozhraní, kde je parametr obecného typu T kovariantní, a rozhraní, kde je kontravariantní, pokud v rozšiřujícím rozhraní je parametr T obecného typu invariantní. To je znázorněno v následujícím příkladu kódu.

interface ICovariant<out T> { }
interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }

Pokud je však parametr T obecného typu deklarován kovariant v jednom rozhraní, nelze jej deklarovat kontravariant v rozšiřujícím rozhraní nebo naopak. To je znázorněno v následujícím příkladu kódu.

interface ICovariant<out T> { }
// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }

Zabránění nejednoznačnosti

Při implementaci obecných rozhraní variant může rozptyl někdy vést k nejednoznačnosti. Těmto nejednoznačnostem byste se měli vyhnout.

Pokud například explicitně implementujete stejné obecné rozhraní variant s různými parametry obecného typu v jedné třídě, může vytvořit nejednoznačnost. Kompilátor v tomto případě nevygeneruje chybu, ale není určena, která implementace rozhraní bude vybrána za běhu. Tato nejednoznačnost může vést k drobným chybám v kódu. Uvažujte následující příklad kódu.

// Simple class hierarchy.
class Animal { }
class Cat : Animal { }
class Dog : Animal { }

// This class introduces ambiguity
// because IEnumerable<out T> is covariant.
class Pets : IEnumerable<Cat>, IEnumerable<Dog>
{
    IEnumerator<Cat> IEnumerable<Cat>.GetEnumerator()
    {
        Console.WriteLine("Cat");
        // Some code.
        return null;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        // Some code.
        return null;
    }

    IEnumerator<Dog> IEnumerable<Dog>.GetEnumerator()
    {
        Console.WriteLine("Dog");
        // Some code.
        return null;
    }
}
class Program
{
    public static void Test()
    {
        IEnumerable<Animal> pets = new Pets();
        pets.GetEnumerator();
    }
}

V tomto příkladu není určeno, jak pets.GetEnumerator se metoda mezi Cat a Dog. To může způsobit problémy v kódu.

Viz také