Recherche…


Syntaxe

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

Paramètres

Paramètres) La description
LA TÉLÉ Tapez les espaces réservés pour les déclarations génériques

Remarques

Les génériques en C # sont pris en charge jusqu'au moment de l'exécution: les types génériques construits avec C # verront leur sémantique générique préservée même après la compilation en CIL .

Cela signifie que, en C #, il est possible de réfléchir aux types génériques et de les voir tels qu'ils ont été déclarés ou de vérifier si un objet est une instance de type générique, par exemple. Ceci est en contraste avec l' effacement de type , où les informations de type génériques sont supprimées lors de la compilation. Cela contraste également avec l'approche par modèle des génériques, où plusieurs types génériques concrets deviennent de multiples types non génériques à l'exécution, et toutes les métadonnées requises pour instancier davantage les définitions de type génériques d'origine sont perdues.

Soyez prudent, cependant, lorsque vous réfléchissez sur des types génériques: les noms des types génériques seront modifiés lors de la compilation, en remplaçant les noms entre parenthèses et les paramètres de type par un backtick suivi du nombre de paramètres de type génériques. Ainsi, un Dictionary<TKey, Tvalue> sera traduit en Dictionary`2 .

Paramètres de type (classes)

Déclaration:

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

Initialisation:

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

Utilisation (comme type de paramètre):

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

Paramètres de type (méthodes)

Déclaration:

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

Invocation:

Il n'est pas nécessaire de fournir des arguments de type à une méthode générique, car le compilateur peut implicitement déduire le type.

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

Cependant, s’il ya une ambiguïté, les méthodes génériques doivent être appelées avec des arguments de type

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

Paramètres de type (interfaces)

Déclaration:

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

Utilisation (en héritage):

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> { ... }

Utilisation (comme type de paramètre):

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

Inférence de type implicite (méthodes)

Lors de la transmission d'arguments formels à une méthode générique, les arguments de type générique pertinents peuvent généralement être déduits implicitement. Si tous les types génériques peuvent être déduits, leur spécification dans la syntaxe est facultative.

Considérons la méthode générique suivante. Il a un paramètre formel et un paramètre de type générique. Il existe une relation très évidente entre eux - le type passé en argument au paramètre de type générique doit être le même que le type de compilation de l’argument passé au paramètre formel.

void M<T>(T obj)
{
}

Ces deux appels sont équivalents:

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

Ces deux appels sont aussi équivalents:

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

Et ainsi sont ces trois appels:

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

Notez que si au moins un argument de type ne peut être déduit, tous doivent être spécifiés.

Considérons la méthode générique suivante. Le premier argument de type générique est le même que le type de l'argument formel. Mais il n'y a pas une telle relation pour le second argument de type générique. Par conséquent, le compilateur n'a aucun moyen d'inférer le deuxième argument de type générique dans aucun appel à cette méthode.

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

Cela ne fonctionne plus:

X("");

Cela ne fonctionne pas non plus, car le compilateur ne sait pas si nous spécifions le premier ou le deuxième paramètre générique (les deux seraient valables comme object ):

X<object>("");

Nous sommes tenus de les taper tous les deux, comme ceci:

X<string, object>("");

Contraintes de type (classes et interfaces)

Les contraintes de type peuvent forcer un paramètre de type à implémenter une certaine interface ou classe.

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

Syntaxe pour plusieurs contraintes:

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

Les contraintes de type fonctionnent de la même manière que l'héritage, car il est possible de spécifier plusieurs interfaces en tant que contraintes sur le type générique, mais une seule classe:

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

Une autre règle est que la classe doit être ajoutée en tant que première contrainte, puis les interfaces:

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

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

Toutes les contraintes déclarées doivent être satisfaites simultanément pour qu'une instanciation générique particulière fonctionne. Il n'y a aucun moyen de spécifier deux ou plusieurs autres ensembles de contraintes.

Contraintes de type (class et struct)

Il est possible de spécifier si l'argument de type doit ou non être un type de référence ou un type de valeur en utilisant la class ou la struct contraintes correspondante. Si ces contraintes sont utilisées, elles doivent être définies avant que toutes les autres contraintes (par exemple, un type parent ou new() ) puissent être répertoriées.

// 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
    {
    }
}

Contraintes de type (new-mot-clé)

En utilisant la contrainte new() , il est possible d'imposer des paramètres de type pour définir un constructeur vide (par défaut).

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.

Le deuxième appel à Create() donnera une erreur de compilation avec le message suivant:

'Bar' doit être un type non abstrait avec un constructeur public sans paramètre afin de l'utiliser comme paramètre 'T' dans le type générique ou la méthode 'Factory'

Il n'y a pas de contrainte pour un constructeur avec des paramètres, seuls les constructeurs sans paramètre sont pris en charge.

Type d'inférence (classes)

Les développeurs peuvent être surpris par le fait que l'inférence de type ne fonctionne pas pour les 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.

La première manière de créer une instance sans spécifier explicitement les paramètres de type entraînera une erreur de compilation qui indiquerait:

L'utilisation du type générique 'Tuple <T1, T2>' nécessite 2 arguments de type

Une solution commune consiste à ajouter une méthode d'assistance dans une classe statique:

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...

Réflexion sur les paramètres de type

L'opérateur typeof fonctionne sur les paramètres de type.

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

Paramètres de type explicites

Il existe différents cas où vous devez spécifier explicitement les paramètres de type pour une méthode générique. Dans les deux cas ci-dessous, le compilateur ne peut pas déduire tous les paramètres de type des paramètres de méthode spécifiés.

Un cas est quand il n'y a pas de paramètres:

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

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

Le deuxième cas est celui où un (ou plusieurs) des paramètres de type ne fait pas partie des paramètres de la méthode:

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.

Utiliser une méthode générique avec une interface en tant que type de contrainte.

Voici un exemple d'utilisation du type générique TFood dans la méthode Eat sur la classe 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);
    }
}

Vous pouvez appeler la méthode Eat comme ceci:

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

Dans ce cas, si vous essayez d'appeler:

sheep.Eat(lion);

Cela ne sera pas possible car l'objet lion n'implémente pas l'interface IFood. Tenter de faire l'appel ci-dessus générera une erreur de compilation: "Le type 'Carnivore' ne peut pas être utilisé comme paramètre de type 'TFood' dans le type générique ou la méthode 'Animal.Eat (TFood)'. Carnivore 'à' IFood '. "

Covariance

Quand un IEnumerable<T> un sous-type d'un autre IEnumerable<T1> ? Lorsque T est un sous-type de T1 . IEnumerable est covariant dans son paramètre T , ce qui signifie que la relation de sous-type d' IEnumerable va dans le même sens que celle de T

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>

Une instance d'un type générique covariant avec un paramètre de type donné est implicitement convertible dans le même type générique avec un paramètre de type moins dérivé.

Cette relation est valable car IEnumerable produit des T s mais ne les consomme pas. Un objet qui produit un Dog peut être utilisé comme s'il produisait un Animal .

Les paramètres de type de covariant sont déclarés à l'aide du mot clé out , car le paramètre ne doit être utilisé qu'en tant que sortie .

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

Un paramètre de type déclaré comme covariant peut ne pas apparaître comme une entrée.

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

Voici un exemple complet:

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

Quand un IComparer<T> un sous-type d'un autre IComparer<T1> ? Lorsque T1 est un sous-type de T IComparer est contravariant dans son paramètre T , ce qui signifie que la relation de sous-type d' IComparer va dans la direction opposée à celle de T

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>

Une instance d'un type générique contravariant avec un paramètre de type donné est implicitement convertible dans le même type générique avec un paramètre de type plus dérivé.

Cette relation est valable car IComparer consomme T s mais ne les produit pas. Un objet qui peut comparer deux Animal peut être utilisé pour comparer deux Dog .

Les paramètres de type contravariant sont déclarés à l'aide du mot-clé in , car le paramètre ne doit être utilisé qu'en entrée .

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

Un paramètre de type déclaré comme contravariant peut ne pas apparaître comme une sortie.

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

Invariance

IList<T> n'est jamais un sous-type d'un IList<T1> différent IList<T1> . IList est invariant dans son paramètre type.

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

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

Il n'y a pas de relation de sous-type pour les listes car vous pouvez mettre des valeurs dans une liste et en extraire des valeurs.

Si IList était covariant, vous pourriez ajouter des éléments du sous-type incorrect à une liste donnée.

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

Si IList était contravariant, vous pourriez extraire des valeurs du sous-type incorrect d'une liste donnée.

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!

Les paramètres de type invariant sont déclarés en omettant les mots-clés in et out .

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

Interfaces de variantes

Les interfaces peuvent avoir des paramètres de type variant.

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

mais les classes et les structures ne peuvent pas

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

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

ni faire des déclarations de méthodes génériques

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

L'exemple ci-dessous montre plusieurs déclarations de variance sur la même 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>

Variantes de délégués

Les délégués peuvent avoir des paramètres de type variant.

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

Cela découle du principe de substitution de Liskov , qui stipule (entre autres) qu’une méthode D peut être considérée comme plus dérivée qu’une méthode B si:

  • D a un type de retour dérivé égal ou supérieur à B
  • D a des types de paramètres correspondants égaux ou plus généraux que B

Par conséquent, les affectations suivantes sont toutes de type sécurisé:

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

Types de variantes en tant que paramètres et valeurs de retour

Si un type de covariant apparaît en sortie, le type contenant est covariant. Produire un producteur de T est comme produire des T

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

Si un type contravariant apparaît en tant que sortie, le type contenant est contravariant. Produire un consommateur de T s, c'est comme consommer des T s.

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

Si un type de covariant apparaît en entrée, le type contenant est contravariant. Consommer un producteur de T est comme consommer des T

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

Si un type contravariant apparaît en entrée, le type contenant est covariant. Consommer un consommateur de T est comme produire des T

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

Vérification de l'égalité des valeurs génériques.

Si la logique de la classe ou de la méthode générique nécessite de vérifier l’égalité des valeurs de type générique, utilisez la propriété EqualityComparer<TType>.Default :

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

Cette approche est mieux que d'appeler Object.Equals() méthode, parce que les contrôles de mise en œuvre de comparateur par défaut, si TBar Type implémente IEquatale<TBar> Interface et si oui, appelle IEquatable<TBar>.Equals(TBar other) méthode. Cela permet d'éviter la boxe / unboxing des types de valeur.

Casting de type générique

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

        

    }

Coutumes:

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

Lecteur de configuration avec conversion de type générique

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

    }

Coutumes:

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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow