Delen via


Variant generic Interfaces maken (C#)

U kunt algemene typeparameters in interfaces declareren als covariant of contravariant. Met covariantie kunnen interfacemethoden meer afgeleide retourtypen hebben dan die zijn gedefinieerd door de algemene typeparameters. Met Contravariantie kunnen interfacemethoden argumenttypen hebben die minder zijn afgeleid dan die zijn opgegeven door de algemene parameters. Een algemene interface met covariant of contravariant algemene typeparameters wordt variant genoemd.

Notitie

.NET Framework 4 heeft variantieondersteuning geïntroduceerd voor verschillende bestaande algemene interfaces. Zie Variantie in Algemene interfaces (C#) voor de lijst met variantinterfaces in .NET.

Het declareren van algemene variantinterfaces

U kunt variant generic interfaces declareren met behulp van de in en out trefwoorden voor algemene typeparameters.

Belangrijk

ref, inen out parameters in C# kunnen geen variant zijn. Waardetypen bieden ook geen ondersteuning voor variantie.

U kunt een covariant van een algemene typeparameter declareren met behulp van het out trefwoord. Het covarianttype moet aan de volgende voorwaarden voldoen:

  • Het type wordt alleen gebruikt als een retourtype interfacemethoden en niet gebruikt als een type methodeargumenten. Dit wordt geïllustreerd in het volgende voorbeeld, waarin het type R covariant wordt gedeclareerd.

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

    Er is één uitzondering op deze regel. Als u een algemene gemachtigde met contravariant als methodeparameter hebt, kunt u het type gebruiken als een algemene typeparameter voor de gemachtigde. Dit wordt geïllustreerd door het type R in het volgende voorbeeld. Zie Variantie in Gemachtigden (C#) en Variantie gebruiken voor Func en Action Generic Delegates (C#) voor meer informatie.

    interface ICovariant<out R>
    {
        void DoSomething(Action<R> callback);
    }
    
  • Het type wordt niet gebruikt als een algemene beperking voor de interfacemethoden. Dit wordt geïllustreerd in de volgende code.

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

U kunt een algemeen type parameter contravariant declareren met behulp van het in trefwoord. Het contravarianttype kan alleen worden gebruikt als een type methodeargumenten en niet als een retourtype interfacemethoden. Het type contravariant kan ook worden gebruikt voor algemene beperkingen. De volgende code laat zien hoe u een contravariant-interface declareert en een algemene beperking gebruikt voor een van de methoden.

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

Het is ook mogelijk om covariantie en contravariantie in dezelfde interface te ondersteunen, maar voor verschillende typeparameters, zoals wordt weergegeven in het volgende codevoorbeeld.

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

Algemene variantinterfaces implementeren

U implementeert variant-algemene interfaces in klassen met behulp van dezelfde syntaxis die wordt gebruikt voor invariante interfaces. In het volgende codevoorbeeld ziet u hoe u een covariant-interface in een algemene klasse implementeert.

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

Klassen die variantinterfaces implementeren, zijn invariant. Denk bijvoorbeeld aan de volgende code.

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

Uitbreiding van algemene variantinterfaces

Wanneer u een algemene variantinterface uitbreidt, moet u de in en out trefwoorden gebruiken om expliciet op te geven of de afgeleide interface variantie ondersteunt. De compiler leidt niet af van de variantie van de interface die wordt uitgebreid. Denk bijvoorbeeld aan de volgende interfaces.

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

In de IInvariant<T> interface is de algemene typeparameter T invariant, terwijl in IExtCovariant<out T> de typeparameter covariant is, hoewel beide interfaces dezelfde interface uitbreiden. Dezelfde regel wordt toegepast op parameters van het algemene type contravariant.

U kunt een interface maken die zowel de interface uitbreidt als de algemene typeparameter T covariant is en de interface waar deze contravariant is als de parameter voor het algemene type T invariant is in de uitbreidingsinterface. Dit wordt geïllustreerd in het volgende codevoorbeeld.

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

Als een algemene typeparameter T echter covariant in één interface wordt gedeclareerd, kunt u deze niet declareren in de uitbreidingsinterface of omgekeerd. Dit wordt geïllustreerd in het volgende codevoorbeeld.

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

Dubbelzinnigheid vermijden

Wanneer u generieke variantinterfaces implementeert, kan afwijking soms leiden tot dubbelzinnigheid. Dergelijke dubbelzinnigheid moet worden vermeden.

Als u bijvoorbeeld expliciet dezelfde algemene variantinterface met verschillende algemene typeparameters in één klasse implementeert, kan dit dubbelzinnigheid creëren. De compiler produceert in dit geval geen fout, maar wordt niet opgegeven welke interface-implementatie tijdens runtime wordt gekozen. Deze dubbelzinnigheid kan leiden tot subtiele fouten in uw code. Kijk eens naar het volgende codevoorbeeld.

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

In dit voorbeeld wordt niet aangegeven hoe de pets.GetEnumerator methode kiest tussen Cat en Dog. Dit kan problemen in uw code veroorzaken.

Zie ook