Zoeken…


Syntaxis

  • public void SomeMethod <T> () { }
  • public void SomeMethod<T, V>() { }
  • public T SomeMethod<T>(IEnumerable<T> sequence) { ... }
  • public void SomeMethod<T>() where T : new() { }
  • public void SomeMethod<T, V>() where T : new() where V : struct { }
  • public void SomeMethod<T>() where T: IDisposable { }
  • public void SomeMethod<T>() where T: Foo { }
  • public class MyClass<T> { public T Data {get; set; } }

parameters

Parameter (s) Beschrijving
T, V Typ tijdelijke aanduidingen voor generieke verklaringen

Opmerkingen

Generics in C # worden tot in de runtime ondersteund: generieke types gebouwd met C # behouden hun generieke semantiek, zelfs nadat ze zijn gecompileerd naar CIL .

Dit betekent effectief dat het in C # mogelijk is om te reflecteren op generieke typen en ze te zien zoals ze zijn gedeclareerd of om te controleren of een object bijvoorbeeld een instantie van een generiek type is. Dit in tegenstelling tot type wissen , waarbij generieke type-informatie wordt verwijderd tijdens het compileren. Het is ook in tegenstelling tot de sjabloonbenadering van generieke geneesmiddelen, waarbij meerdere concrete generieke typen tijdens runtime meerdere niet-generieke typen worden en alle metagegevens die nodig zijn om de oorspronkelijke generieke typedefinities verder te instantiëren, verloren gaan.

Wees echter voorzichtig bij het nadenken over generieke typen: de namen van generieke typen worden bij het compileren gewijzigd, waarbij de haakjes en de namen van de typeparameters worden vervangen door een backtick gevolgd door het aantal generieke typeparameters. Dus zal een Dictionary<TKey, Tvalue> worden vertaald naar Dictionary`2 .

Type Parameters (Klassen)

Verklaring:

class MyGenericClass<T1, T2, T3, ...>
{
    // Do something with the type parameters.
}

initialisatie:

var x = new MyGenericClass<int, char, bool>();

Gebruik (als het type van een parameter):

void AnotherMethod(MyGenericClass<float, byte, char> arg) { ... }

Type Parameters (Methoden)

Verklaring:

void MyGenericMethod<T1, T2, T3>(T1 a, T2 b, T3 c)
{
    // Do something with the type parameters.
}

Aanroeping:

Het is niet nodig om typeargumenten aan een genetische methode te leveren, omdat de compiler impliciet het type kan afleiden.

int x =10;
int y =20;
string z = "test";
MyGenericMethod(x,y,z);

Als er echter een dubbelzinnigheid is, moeten generieke methoden worden aangeroepen met type arguemnts als

MyGenericMethod<int, int, string>(x,y,z);

Type Parameters (Interfaces)

Verklaring:

interface IMyGenericInterface<T1, T2, T3, ...> { ... }

Gebruik (in erfenis):

class ClassA<T1, T2, T3> : IMyGenericInterface<T1, T2, T3> { ... }

class ClassB<T1, T2> : IMyGenericInterface<T1, T2, int> { ... }

class ClassC<T1> : IMyGenericInterface<T1, char, int> { ... }

class ClassD : IMyGenericInterface<bool, char, int> { ... }

Gebruik (als het type van een parameter):

void SomeMethod(IMyGenericInterface<int, char, bool> arg) { ... }

Impliciete type-inferentie (methoden)

Bij het doorgeven van formele argumenten aan een generieke methode, kunnen relevante generieke type argumenten meestal impliciet worden afgeleid. Als alle generieke typen kunnen worden afgeleid, is het opgeven van deze in de syntaxis optioneel.

Overweeg de volgende generieke methode. Het heeft één formele parameter en één generieke typeparameter. Er is een zeer voor de hand liggende relatie tussen hen - het type dat wordt doorgegeven als een argument voor de generieke parameter type moet hetzelfde zijn als het compilatie-type van het argument dat wordt doorgegeven aan de formele parameter.

void M<T>(T obj)
{
}

Deze twee oproepen zijn gelijkwaardig:

M<object>(new object());
M(new object());

Deze twee oproepen zijn ook equivalent:

M<string>("");
M("");

En zo zijn deze drie oproepen:

M<object>("");
M((object) "");
M("" as object);

Merk op dat als ten minste één type argument niet kan worden afgeleid, ze allemaal moeten worden gespecificeerd.

Overweeg de volgende generieke methode. Het eerste generieke type argument is hetzelfde als het type van het formele argument. Maar er is geen dergelijke relatie voor het tweede generieke type argument. Daarom kan de compiler op geen enkele manier het tweede argument van het generieke type afleiden in een aanroep van deze methode.

void X<T1, T2>(T1 obj)
{
}

Dit werkt niet meer:

X("");

Dit werkt ook niet, omdat de compiler niet zeker weet of we de eerste of de tweede generieke parameter opgeven (beide zouden als object geldig zijn):

X<object>("");

We moeten ze allebei als volgt typen:

X<string, object>("");

Typebeperkingen (klassen en interfaces)

Typebeperkingen kunnen een typeparameter forceren om een bepaalde interface of klasse te implementeren.

interface IType;
interface IAnotherType;

// T must be a subtype of IType
interface IGeneric<T>
    where T : IType
{
}

// T must be a subtype of IType
class Generic<T>
    where T : IType
{
}

class NonGeneric
{
    // T must be a subtype of IType
    public void DoSomething<T>(T arg)
        where T : IType
    {
    }
}

// Valid definitions and expressions:
class Type : IType { }
class Sub : IGeneric<Type> { }
class Sub : Generic<Type> { }
new NonGeneric().DoSomething(new Type());

// Invalid definitions and expressions:
class AnotherType : IAnotherType { }
class Sub : IGeneric<AnotherType> { }
class Sub : Generic<AnotherType> { }
new NonGeneric().DoSomething(new AnotherType());

Syntaxis voor meerdere beperkingen:

class Generic<T, T1>
    where T : IType 
    where T1 : Base, new()
{
}

Typebeperkingen werken op dezelfde manier als overerving, omdat het mogelijk is om meerdere interfaces op te geven als beperkingen voor het generieke type, maar slechts één klasse:

class A { /* ... */ }
class B { /* ... */ }

interface I1 { }
interface I2 { }

class Generic<T>
    where T : A, I1, I2
{
}

class Generic2<T>
    where T : A, B //Compilation error
{
}

Een andere regel is dat de klasse als eerste beperking moet worden toegevoegd en vervolgens de interfaces:

class Generic<T>
    where T : A, I1
{
}

class Generic2<T>
    where T : I1, A //Compilation error
{
}

Aan alle aangegeven beperkingen moet tegelijkertijd worden voldaan om een bepaalde generieke instantiatie te laten werken. Er is geen manier om twee of meer alternatieve sets beperkingen op te geven.

Typebeperkingen (klasse en struct)

Er kunnen aangeven of het type argument een verwijzingstype of waardetype door het respectievelijke mogen worden opgelegd class of struct . Als deze beperkingen worden gebruikt, moeten ze worden gedefinieerd voordat alle andere beperkingen (bijvoorbeeld een oudertype of new() ) kunnen worden vermeld.

// TRef must be a reference type, the use of Int32, Single, etc. is invalid.
// Interfaces are valid, as they are reference types
class AcceptsRefType<TRef>
    where TRef : class
{
    // TStruct must be a value type.
    public void AcceptStruct<TStruct>()
        where TStruct : struct
    {
    }

    // If multiple constraints are used along with class/struct
    // then the class or struct constraint MUST be specified first
    public void Foo<TComparableClass>()
        where TComparableClass : class, IComparable
    {
    }
}

Typebeperkingen (nieuw trefwoord)

Door de new() beperking te gebruiken, is het mogelijk om typeparameters af te dwingen om een lege (standaard) constructor te definiëren.

class Foo
{
    public Foo () { }
}

class Bar
{
    public Bar (string s) { ... }
}

class Factory<T>
    where T : new()
{
    public T Create()
    {
        return new T();
    }
}

Foo f = new Factory<Foo>().Create(); // Valid.
Bar b = new Factory<Bar>().Create(); // Invalid, Bar does not define a default/empty constructor.

De tweede aanroep naar Create() geeft een compilatietijdfout met het volgende bericht:

'Bar' moet een niet-abstract type zijn met een openbare parameterloze constructor om het te gebruiken als parameter 'T' in het generieke type of de methode 'Factory'

Er is geen beperking voor een constructor met parameters, alleen parameterloze constructors worden ondersteund.

Type inferentie (klassen)

Ontwikkelaars kunnen worden opgemerkt door het feit dat type-inferentie niet werkt voor constructeurs:

class Tuple<T1,T2>
{
   public Tuple(T1 value1, T2 value2)
   {
   }
}

var x = new Tuple(2, "two");              // This WON'T work...
var y = new Tuple<int, string>(2, "two"); // even though the explicit form will.

De eerste manier om een instantie te maken zonder expliciet typeparameters op te geven, veroorzaakt een compilatietijdfout die zou zeggen:

Het gebruik van het generieke type 'Tuple <T1, T2>' vereist 2 type argumenten

Een gebruikelijke oplossing is het toevoegen van een helpermethode in een statische klasse:

static class Tuple
{
    public static Tuple<T1, T2> Create<T1, T2>(T1 value1, T2 value2)
    {
         return new Tuple<T1, T2>(value1, value2);
    }
}

var x = Tuple.Create(2, "two");  // This WILL work...

Reflecteren op typeparameters

De operator typeof werkt op typeof .

class NameGetter<T>
{
    public string GetTypeName()
    {
        return typeof(T).Name;
    }
}

Expliciete typeparameters

Er zijn verschillende gevallen waarin u de typeparameters voor een generieke methode expliciet moet opgeven. In beide onderstaande gevallen kan de compiler niet alle typeparameters afleiden uit de opgegeven methodeparameters.

In één geval zijn er geen parameters:

public void SomeMethod<T, V>() 
{
   // No code for simplicity
}

SomeMethod(); // doesn't compile
SomeMethod<int, bool>(); // compiles

In het tweede geval is een (of meer) van de typeparameters geen onderdeel van de methodeparameters:

public K SomeMethod<K, V>(V input)
{
    return default(K);
}

int num1 = SomeMethod(3); // doesn't compile
int num2 = SomeMethod<int>("3"); // doesn't compile
int num3 = SomeMethod<int, string>("3"); // compiles.

Gebruik van een generieke methode met een interface als een beperkingstype.

Dit is een voorbeeld van het gebruik van de generieke TFood-methode binnen Eat op de klasse Animal

public interface IFood
{
    void EatenBy(Animal animal);
}

public class Grass: IFood
{
    public void EatenBy(Animal animal)
    {
        Console.WriteLine("Grass was eaten by: {0}", animal.Name);
    }
}

public class Animal
{
    public string Name { get; set; }

    public void Eat<TFood>(TFood food)
        where TFood : IFood
    {
        food.EatenBy(this);
    }
}

public class Carnivore : Animal
{
    public Carnivore()
    {
        Name = "Carnivore";
    }
}

public class Herbivore : Animal, IFood
{
    public Herbivore()
    {
        Name = "Herbivore";
    }
    
    public void EatenBy(Animal animal)
    {
        Console.WriteLine("Herbivore was eaten by: {0}", animal.Name);
    }
}

Je kunt de Eat-methode als volgt aanroepen:

var grass = new Grass();        
var sheep = new Herbivore();
var lion = new Carnivore();
    
sheep.Eat(grass);
//Output: Grass was eaten by: Herbivore

lion.Eat(sheep);
//Output: Herbivore was eaten by: Carnivore

In dit geval als u probeert te bellen:

sheep.Eat(lion);

Het is niet mogelijk omdat de objectleeuw de interface IFood niet implementeert. Als u probeert de bovenstaande aanroep te doen, wordt een compilerfout gegenereerd: 'Het type' Carnivore 'kan niet worden gebruikt als typeparameter' TFood 'in het generieke type of de methode' Animal.Eat (TFood) '. Er is geen impliciete referentieconversie van' Carnivore 'to' IFood '. "

covariantie

Wanneer is een IEnumerable<T> een subtype van een andere IEnumerable<T1> ? Wanneer T een subtype van T1 . IEnumerable is covariant in zijn T parameter, wat betekent dat de subtype relatie van IEnumerable in dezelfde richting gaat als T 's.

class Animal { /* ... */ }
class Dog : Animal { /* ... */ }

IEnumerable<Dog> dogs = Enumerable.Empty<Dog>();
IEnumerable<Animal> animals = dogs;  // IEnumerable<Dog> is a subtype of IEnumerable<Animal>
// dogs = animals;  // Compilation error - IEnumerable<Animal> is not a subtype of IEnumerable<Dog>

Een exemplaar van een covariant generiek type met een gegeven parameter type is impliciet converteerbaar naar hetzelfde generieke type met een minder afgeleide parameter type.

Deze relatie geldt omdat IEnumerable T 's produceert maar ze niet verbruikt. Een object dat Dog produceert, kan worden gebruikt alsof het Animal produceert.

Parameters van het type Covariant worden gedeclareerd met het trefwoord out , omdat de parameter alleen als uitvoer moet worden gebruikt.

interface IEnumerable<out T> { /* ... */ }

Een typeparameter die als covariant is aangegeven, wordt mogelijk niet als invoer weergegeven.

interface Bad<out T>
{
    void SetT(T t);  // type error
}

Hier is een compleet voorbeeld:

using NUnit.Framework;

namespace ToyStore
{
   enum Taste { Bitter, Sweet };

   interface IWidget
   {
      int Weight { get; }
   }

   interface IFactory<out TWidget>
       where TWidget : IWidget
   {
      TWidget Create();
   }

   class Toy : IWidget
   {
      public int Weight { get; set; }
      public Taste Taste { get; set; }
   }

   class ToyFactory : IFactory<Toy>
   {
      public const int StandardWeight = 100;
      public const Taste StandardTaste = Taste.Sweet;

      public Toy Create() { return new Toy { Weight = StandardWeight, Taste = StandardTaste }; }
   }

   [TestFixture]
   public class GivenAToyFactory
   {
      [Test]
      public static void WhenUsingToyFactoryToMakeWidgets()
      {
         var toyFactory = new ToyFactory();

         //// Without out keyword, note the verbose explicit cast:
         // IFactory<IWidget> rustBeltFactory = (IFactory<IWidget>)toyFactory;

         // covariance: concrete being assigned to abstract (shiny and new)
         IFactory<IWidget> widgetFactory = toyFactory;
         IWidget anotherToy = widgetFactory.Create();
         Assert.That(anotherToy.Weight, Is.EqualTo(ToyFactory.StandardWeight)); // abstract contract
         Assert.That(((Toy)anotherToy).Taste, Is.EqualTo(ToyFactory.StandardTaste)); // concrete contract
      }
   }
}

contravariance

Wanneer is een IComparer<T> een subtype van een andere IComparer<T1> ? Wanneer T1 een subtype van T . IComparer is contravariant in zijn T parameter, wat betekent dat de subtype relatie van IComparer in de tegenovergestelde richting gaat als T 's.

class Animal { /* ... */ }
class Dog : Animal { /* ... */ }

IComparer<Animal> animalComparer = /* ... */;
IComparer<Dog> dogComparer = animalComparer;  // IComparer<Animal> is a subtype of IComparer<Dog>
// animalComparer = dogComparer;  // Compilation error - IComparer<Dog> is not a subtype of IComparer<Animal>

Een exemplaar van een contravariant generiek type met een gegeven type parameter is impliciet converteerbaar naar hetzelfde generieke type met een meer afgeleide type parameter.

Deze relatie geldt omdat IComparer T s verbruikt maar niet produceert. Een object dat twee willekeurige Animal 's kan vergelijken, kan worden gebruikt om twee Dog 's te vergelijken.

Contravariante systeemparameters worden gedeclareerd met de in zoekwoord, omdat de parameter alleen als ingang worden gebruikt.

interface IComparer<in T> { /* ... */ }

Een typeparameter die als contravariant is aangegeven, wordt mogelijk niet als uitvoer weergegeven.

interface Bad<in T>
{
    T GetT();  // type error
}

invariantie

IList<T> is nooit een subtype van een andere IList<T1> . IList is invariant in zijn IList .

class Animal { /* ... */ }
class Dog : Animal { /* ... */ }

IList<Dog> dogs = new List<Dog>();
IList<Animal> animals = dogs;  // type error

Er is geen subtype-relatie voor lijsten omdat u waarden in een lijst kunt plaatsen en waarden uit een lijst kunt verwijderen.

Als IList covariant was, zou je items van het verkeerde subtype aan een bepaalde lijst kunnen toevoegen.

IList<Animal> animals = new List<Dog>();  // supposing this were allowed...
animals.Add(new Giraffe());  // ... then this would also be allowed, which is bad!

Als IList was, zou je waarden van het verkeerde subtype uit een bepaalde lijst kunnen halen.

IList<Dog> dogs = new List<Animal> { new Dog(), new Giraffe() };  // if this were allowed...
Dog dog = dogs[1];  // ... then this would be allowed, which is bad!

Invariante typeparameters worden gedeclareerd door zowel de in als- out sleutelwoorden weg te laten.

interface IList<T> { /* ... */ }

Verschillende interfaces

Interfaces kunnen parameters van het varianttype hebben.

interface IEnumerable<out T>
{
    // ...
}
interface IComparer<in T>
{
    // ...
}

maar klassen en structuren misschien niet

class BadClass<in T1, out T2>  // not allowed
{
}

struct BadStruct<in T1, out T2>  // not allowed
{
}

generieke methodeverklaringen ook niet

class MyClass
{
    public T Bad<out T, in T1>(T1 t1)  // not allowed
    {
        // ...
    }
}

Het onderstaande voorbeeld toont meerdere variantie-verklaringen op dezelfde interface

interface IFoo<in T1, out T2, T3>
//  T1 : Contravariant type
//  T2 : Covariant type 
//  T3 : Invariant type
{
    // ...
}

IFoo<Animal, Dog, int> foo1 = /* ... */;
IFoo<Dog, Animal, int> foo2 = foo1;  
// IFoo<Animal, Dog, int> is a subtype of IFoo<Dog, Animal, int>

Verschillende afgevaardigden

Afgevaardigden kunnen parameters van het varianttype hebben.

delegate void Action<in T>(T t);    // T is an input
delegate T Func<out T>();           // T is an output
delegate T2 Func<in T1, out T2>();  // T1 is an input, T2 is an output

Dit volgt uit het Liskov-vervangingsprincipe , dat (onder andere) stelt dat een methode D als meer afgeleid kan worden beschouwd dan een methode B als:

  • D heeft een gelijk of meer afgeleid retourtype dan B
  • D heeft gelijke of meer algemene overeenkomstige parametertypen dan B

Daarom zijn de volgende opdrachten allemaal type-veilig:

Func<object, string> original = SomeMethod;
Func<object, object> d1 = original;
Func<string, string> d2 = original;
Func<string, object> d3 = original;

Varianttypen als parameters en retourwaarden

Als een covarianttype als uitvoer wordt weergegeven, is het bevattende type covariant. Het produceren van een producent van T is als het produceren van T .

interface IReturnCovariant<out T>
{
    IEnumerable<T> GetTs();
}

Als een contravariant type als uitvoer verschijnt, is het bevattende type contravariant. Het produceren van een consument van T is als het consumeren van T .

interface IReturnContravariant<in T>
{
    IComparer<T> GetTComparer();
}

Als een covariant type als invoer verschijnt, is het bevattende type contravariant. Het consumeren van een producent van T is als het consumeren van T .

interface IAcceptCovariant<in T>
{
    void ProcessTs(IEnumerable<T> ts);
}

Als een contravariant type als invoer verschijnt, is het bevattende type covariant. Het consumeren van een consument van T is als het produceren van T .

interface IAcceptContravariant<out T>
{
    void CompareTs(IComparer<T> tComparer);
}

Controle van de gelijkheid van generieke waarden.

Als logica van generieke klasse of methode het controleren van de gelijkheid van waarden van het generieke type vereist, gebruik dan EqualityComparer<TType>.Default eigenschap :

public void Foo<TBar>(TBar arg1, TBar arg2)
{
    var comparer = EqualityComparer<TBar>.Default;
    if (comparer.Equals(arg1,arg2)
    {
        ...
    }
}

Deze aanpak is beter dan alleen de methode Object.Equals() aanroepen, omdat de standaardvergelijkingsimplementatie controleert, of het TBar type de IEquatale<TBar> -interface implementeert en zo ja, de IEquatable<TBar>.Equals(TBar other) methode aanroept. Hiermee kunt u het in / uit dozen van waardetypes voorkomen.

Generiek type casting

    /// <summary>
    /// Converts a data type to another data type.
    /// </summary>
    public static class Cast
    {
        /// <summary>
        /// Converts input to Type of default value or given as typeparam T
        /// </summary>
        /// <typeparam name="T">typeparam is the type in which value will be returned, it could be any type eg. int, string, bool, decimal etc.</typeparam>
        /// <param name="input">Input that need to be converted to specified type</param>
        /// <param name="defaultValue">defaultValue will be returned in case of value is null or any exception occures</param>
        /// <returns>Input is converted in Type of default value or given as typeparam T and returned</returns>
        public static T To<T>(object input, T defaultValue)
        {
            var result = defaultValue;
            try
            {
                if (input == null || input == DBNull.Value) return result;
                if (typeof (T).IsEnum)
                {
                    result = (T) Enum.ToObject(typeof (T), To(input, Convert.ToInt32(defaultValue)));
                }
                else
                {
                    result = (T) Convert.ChangeType(input, typeof (T));
                }
            }
            catch (Exception ex)
            {
                Tracer.Current.LogException(ex);
            }

            return result;
        }
        
        /// <summary>
        /// Converts input to Type of typeparam T
        /// </summary>
        /// <typeparam name="T">typeparam is the type in which value will be returned, it could be any type eg. int, string, bool, decimal etc.</typeparam>
        /// <param name="input">Input that need to be converted to specified type</param>
        /// <returns>Input is converted in Type of default value or given as typeparam T and returned</returns>
        public static T To<T>(object input)
        {
            return To(input, default(T));
        }

        

    }

Gebruik:

std.Name = Cast.To<string>(drConnection["Name"]);
std.Age = Cast.To<int>(drConnection["Age"]);
std.IsPassed = Cast.To<bool>(drConnection["IsPassed"]);


// Casting type using default value
//Following both ways are correct
// Way 1 (In following style input is converted into type of default value)
std.Name = Cast.To(drConnection["Name"], "");
std.Marks = Cast.To(drConnection["Marks"], 0);
// Way 2    
std.Name = Cast.To<string>(drConnection["Name"], "");
std.Marks = Cast.To<int>(drConnection["Marks"], 0);

Configuratie-lezer met generieke typecasting

    /// <summary>
    /// Read configuration values from app.config and convert to specified types
    /// </summary>
    public static class ConfigurationReader
    {
        /// <summary>
        /// Get value from AppSettings by key, convert to Type of default value or typeparam T and return
        /// </summary>
        /// <typeparam name="T">typeparam is the type in which value will be returned, it could be any type eg. int, string, bool, decimal etc.</typeparam>
        /// <param name="strKey">key to find value from AppSettings</param>
        /// <param name="defaultValue">defaultValue will be returned in case of value is null or any exception occures</param>
        /// <returns>AppSettings value against key is returned in Type of default value or given as typeparam T</returns>
        public static T GetConfigKeyValue<T>(string strKey, T defaultValue)
        {
            var result = defaultValue;
            try
            {
                if (ConfigurationManager.AppSettings[strKey] != null)
                    result = (T)Convert.ChangeType(ConfigurationManager.AppSettings[strKey], typeof(T));
            }
            catch (Exception ex)
            {
                Tracer.Current.LogException(ex);
            }

            return result;
        }
        /// <summary>
        /// Get value from AppSettings by key, convert to Type of default value or typeparam T and return
        /// </summary>
        /// <typeparam name="T">typeparam is the type in which value will be returned, it could be any type eg. int, string, bool, decimal etc.</typeparam>
        /// <param name="strKey">key to find value from AppSettings</param>
        /// <returns>AppSettings value against key is returned in Type given as typeparam T</returns>
        public static T GetConfigKeyValue<T>(string strKey)
        {
            return GetConfigKeyValue(strKey, default(T));
        }

    }

Gebruik:

var timeOut = ConfigurationReader.GetConfigKeyValue("RequestTimeout", 2000);
var url = ConfigurationReader.GetConfigKeyValue("URL", "www.someurl.com");
var enabled = ConfigurationReader.GetConfigKeyValue("IsEnabled", false);


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow