Dela via


Skapa generiska variantgränssnitt (C#)

Du kan deklarera generiska typparametrar i gränssnitt som covarianta eller kontravarianta. Med kovarians kan gränssnittsmetoder ha fler härledda returtyper än de som definieras av parametrar av allmän typ. Med kontravarians kan gränssnittsmetoder ha argumenttyper som är mindre härledda än de som anges av de allmänna parametrarna. Ett allmänt gränssnitt som har parametrar av typen covariant eller kontravariant generisk typ kallas variant.

Kommentar

.NET Framework 4 introducerade variansstöd för flera befintliga generiska gränssnitt. Listan över variantgränssnitten i .NET finns i Varians i Generiska gränssnitt (C#).

Deklarera generiska gränssnitt för variant

Du kan deklarera generiska variantgränssnitt med hjälp av nyckelorden in och out för generiska typparametrar.

Viktigt!

ref, inoch out parametrar i C# kan inte vara varianter. Värdetyper stöder inte heller varians.

Du kan deklarera en allmän typparameter covariant med hjälp av nyckelordet out . Den varianta typen måste uppfylla följande villkor:

  • Typen används endast som en returtyp av gränssnittsmetoder och används inte som en typ av metodargument. Detta illustreras i följande exempel, där typen R deklareras som variant.

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

    Det finns ett undantag till den här regeln. Om du har ett kontravariant generiskt ombud som en metodparameter kan du använda typen som en allmän typparameter för ombudet. Detta illustreras av typen R i följande exempel. Mer information finns i Varians i ombud (C#) och Använda varians för func och åtgärd – allmänna ombud (C#).

    interface ICovariant<out R>
    {
        void DoSomething(Action<R> callback);
    }
    
  • Typen används inte som en allmän begränsning för gränssnittsmetoderna. Detta illustreras i följande kod.

    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;
    }
    

Du kan deklarera en generisk typparameter kontravariant med hjälp av nyckelordet in . Den kontravarianta typen kan endast användas som en typ av metodargument och inte som en returtyp av gränssnittsmetoder. Typen contravariant kan också användas för allmänna begränsningar. Följande kod visar hur du deklarerar ett kontravariant gränssnitt och använder en allmän begränsning för någon av dess metoder.

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

Det är också möjligt att stödja både kovarians och kontravarians i samma gränssnitt, men för olika typparametrar, som visas i följande kodexempel.

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

Implementera generiska variantgränssnitt

Du implementerar generiska variantgränssnitt i klasser med samma syntax som används för invarianta gränssnitt. Följande kodexempel visar hur du implementerar ett covariant-gränssnitt i en allmän klass.

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

Klasser som implementerar variantgränssnitt är invarianta. Tänk till exempel på följande kod.

// 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;

Utökar generiska variantgränssnitt

När du utökar ett generiskt variantgränssnitt måste du använda nyckelorden in och out för att uttryckligen ange om det härledda gränssnittet stöder varians. Kompilatorn härleder inte variansen från gränssnittet som utökas. Tänk till exempel på följande gränssnitt.

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

IInvariant<T> I gränssnittet är den generiska typparametern T invariant, medan i IExtCovariant<out T> typparametern är covariant, även om båda gränssnitten utökar samma gränssnitt. Samma regel tillämpas på parametrar av typen contravariant generic type.

Du kan skapa ett gränssnitt som utökar både gränssnittet där den generiska typparametern T är covariant och gränssnittet där det är kontravariant om den generiska typparametern T i utökande gränssnitt är invariant. Detta illustreras i följande kodexempel.

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

Men om en allmän typparameter T deklareras som covariant i ett gränssnitt kan du inte deklarera den som kontravariant i det utökade gränssnittet eller vice versa. Detta illustreras i följande kodexempel.

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

Undvika tvetydighet

När du implementerar generiska variantgränssnitt kan varians ibland leda till tvetydighet. Sådana tvetydigheter bör undvikas.

Om du till exempel uttryckligen implementerar samma generiska variantgränssnitt med olika generiska typparametrar i en klass kan det skapa tvetydighet. Kompilatorn skapar inte något fel i det här fallet, men det anges inte vilken gränssnittsimplementering som ska väljas vid körning. Den här tvetydigheten kan leda till subtila buggar i koden. Ta följande kod som exempel.

// 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();
    }
}

I det här exemplet är det ospecificerat hur pets.GetEnumerator metoden väljer mellan Cat och Dog. Detta kan orsaka problem i koden.

Se även