Varians i ombud (C#)
.NET Framework 3.5 introducerade variansstöd för matchande metodsignaturer med ombudstyper i alla ombud i C#. Det innebär att du kan tilldela ombud inte bara metoder som har matchande signaturer, utan även metoder som returnerar fler härledda typer (kovarians) eller som accepterar parametrar som har mindre härledda typer (kontravarians) än de som anges av ombudstypen. Detta omfattar både allmänna och icke-generiska ombud.
Tänk till exempel på följande kod, som har två klasser och två ombud: generiska och icke-generiska.
public class First { }
public class Second : First { }
public delegate First SampleDelegate(Second a);
public delegate R SampleGenericDelegate<A, R>(A a);
När du skapar ombud av typerna SampleDelegate
eller SampleGenericDelegate<A, R>
kan du tilldela någon av följande metoder till dessa ombud.
// Matching signature.
public static First ASecondRFirst(Second second)
{ return new First(); }
// The return type is more derived.
public static Second ASecondRSecond(Second second)
{ return new Second(); }
// The argument type is less derived.
public static First AFirstRFirst(First first)
{ return new First(); }
// The return type is more derived
// and the argument type is less derived.
public static Second AFirstRSecond(First first)
{ return new Second(); }
Följande kodexempel illustrerar den implicita konverteringen mellan metodsignaturen och ombudstypen.
// Assigning a method with a matching signature
// to a non-generic delegate. No conversion is necessary.
SampleDelegate dNonGeneric = ASecondRFirst;
// Assigning a method with a more derived return type
// and less derived argument type to a non-generic delegate.
// The implicit conversion is used.
SampleDelegate dNonGenericConversion = AFirstRSecond;
// Assigning a method with a matching signature to a generic delegate.
// No conversion is necessary.
SampleGenericDelegate<Second, First> dGeneric = ASecondRFirst;
// Assigning a method with a more derived return type
// and less derived argument type to a generic delegate.
// The implicit conversion is used.
SampleGenericDelegate<Second, First> dGenericConversion = AFirstRSecond;
Fler exempel finns i Using Variance in Delegates (C#) and Using Variance for Func and Action Generic Delegates (C#).
Varians i parametrar av allmän typ
I .NET Framework 4 eller senare kan du aktivera implicit konvertering mellan ombud, så att allmänna ombud som har olika typer som anges av generiska typparametrar kan tilldelas till varandra, om typerna ärvs från varandra enligt variansens krav.
Om du vill aktivera implicit konvertering måste du uttryckligen deklarera generiska parametrar i ett ombud som covariant eller kontravariant med hjälp av nyckelordet in
eller out
.
Följande kodexempel visar hur du kan skapa ett ombud som har en covariant allmän typparameter.
// Type T is declared covariant by using the out keyword.
public delegate T SampleGenericDelegate <out T>();
public static void Test()
{
SampleGenericDelegate <String> dString = () => " ";
// You can assign delegates to each other,
// because the type T is declared covariant.
SampleGenericDelegate <Object> dObject = dString;
}
Om du endast använder avvikelsestöd för att matcha metodsignaturer med ombudstyper och inte använder nyckelorden in
och out
kanske du upptäcker att du ibland kan instansiera ombud med identiska lambda-uttryck eller metoder, men du kan inte tilldela en delegat till en annan.
I följande kodexempel SampleGenericDelegate<String>
kan inte uttryckligen konverteras till SampleGenericDelegate<Object>
, men String
ärver Object
. Du kan åtgärda det här problemet genom att markera den generiska parametern T
med nyckelordet out
.
public delegate T SampleGenericDelegate<T>();
public static void Test()
{
SampleGenericDelegate<String> dString = () => " ";
// You can assign the dObject delegate
// to the same lambda expression as dString delegate
// because of the variance support for
// matching method signatures with delegate types.
SampleGenericDelegate<Object> dObject = () => " ";
// The following statement generates a compiler error
// because the generic type T is not marked as covariant.
// SampleGenericDelegate <Object> dObject = dString;
}
Allmänna ombud som har parametrar av varianttyp i .NET
.NET Framework 4 introducerade variansstöd för generiska typparametrar i flera befintliga generiska ombud:
Action
ombud från System namnområdet, till exempel, Action<T> och Action<T1,T2>Func
ombud från System namnområdet, till exempel, Func<TResult> och Func<T,TResult>Ombudet Predicate<T>
Ombudet Comparison<T>
Ombudet Converter<TInput,TOutput>
Mer information och exempel finns i Using Variance for Func and Action Generic Delegates (C#).
Deklarera parametrar av varianttyp i allmänna ombud
Om ett allmänt ombud har parametrar av typen covariant eller kontravariant allmän typ kan det kallas för ett generiskt variantdelegat.
Du kan deklarera en generisk typparameter covariant i ett allmänt ombud med hjälp av nyckelordet out
. Den covarianta typen kan endast användas som en metodreturtyp och inte som en typ av metodargument. I följande kodexempel visas hur du deklarerar ett samvariant generiskt ombud.
public delegate R DCovariant<out R>();
Du kan deklarera en generisk typparameter kontravariant i ett allmänt ombud med hjälp av nyckelordet in
. Typen contravariant kan endast användas som en typ av metodargument och inte som en metodreturtyp. I följande kodexempel visas hur du deklarerar ett kontravariant generiskt ombud.
public delegate void DContravariant<in A>(A a);
Viktigt!
ref
, in
och out
parametrar i C# kan inte markeras som variant.
Det är också möjligt att stödja både varians och kovarians i samma ombud, men för olika typparametrar. Detta visas i följande exempel.
public delegate R DVariant<in A, out R>(A a);
Instansiera och anropa generiska variantdelegater
Du kan instansiera och anropa variantdelegater precis som du instansierar och anropar invarianta ombud. I följande exempel instansieras ombudet av ett lambda-uttryck.
DVariant<String, String> dvariant = (String str) => str + " ";
dvariant("test");
Kombinera generiska variantdelegater
Kombinera inte variantdelegater. Metoden Combine stöder inte variantdelegatkonvertering och förväntar sig att ombuden ska vara av exakt samma typ. Detta kan leda till ett körningsfel när du kombinerar ombud antingen med hjälp Combine av metoden eller med hjälp av operatorn +
, som du ser i följande kodexempel.
Action<object> actObj = x => Console.WriteLine("object: {0}", x);
Action<string> actStr = x => Console.WriteLine("string: {0}", x);
// All of the following statements throw exceptions at run time.
// Action<string> actCombine = actStr + actObj;
// actStr += actObj;
// Delegate.Combine(actStr, actObj);
Varians i generiska typparametrar för värde- och referenstyper
Avvikelse för generiska typparametrar stöds endast för referenstyper. Det går till exempel DVariant<int>
inte att implicit konvertera till DVariant<Object>
eller DVariant<long>
, eftersom heltal är en värdetyp.
I följande exempel visas att variansen i generiska typparametrar inte stöds för värdetyper.
// The type T is covariant.
public delegate T DVariant<out T>();
// The type T is invariant.
public delegate T DInvariant<T>();
public static void Test()
{
int i = 0;
DInvariant<int> dInt = () => i;
DVariant<int> dVariantInt = () => i;
// All of the following statements generate a compiler error
// because type variance in generic parameters is not supported
// for value types, even if generic type parameters are declared variant.
// DInvariant<Object> dObject = dInt;
// DInvariant<long> dLong = dInt;
// DVariant<Object> dVariantObject = dVariantInt;
// DVariant<long> dVariantLong = dVariantInt;
}