Sök…


Syntax

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

parametrar

Parametern (parametrarna) Beskrivning
T, V Skriv platshållare för generiska deklarationer

Anmärkningar

Generics i C # stöds hela vägen ner till runtime: generiska typer byggda med C # kommer att ha sin generiska semantik bevarad även efter sammanställning till CIL .

Detta betyder effektivt att i C # är det möjligt att reflektera över generiska typer och se dem som de deklarerats eller kontrollera om ett objekt är en instans av en generisk typ, till exempel. Detta är i kontrast till radering av typ , där information om generisk typ tas bort under sammanställningen. Det är också i motsats till mallmetoden för generiska, där flera generiska betongtyper blir flera icke-generiska typer vid körning, och eventuella metadata som krävs för att ytterligare instansera de ursprungliga generiska typdefinitionerna går förlorade.

Var dock försiktig när du reflekterar över generiska typer: generiska typers namn kommer att ändras vid sammanställning, och ersätta de vinklade parenteserna och typparametrarnas namn med en ryggstift följt av antalet parametrar för generisk typ. Således kommer en Dictionary<TKey, Tvalue> att översättas till Dictionary`2 .

Typparametrar (klasser)

Deklaration:

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

initiering:

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

Användning (som typ av parameter):

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

Typparametrar (metoder)

Deklaration:

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

Åkallan:

Det finns inget behov att tillhandahålla typargument till en genrisk metod, eftersom kompilatorn implicit kan dra slutsatsen.

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

Men om det finns en tvetydighet måste generiska metoder kallas för typargument som

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

Typparametrar (gränssnitt)

Deklaration:

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

Användning (i arv):

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

Användning (som typ av parameter):

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

Implicit typinferens (metoder)

När formella argument överförs till en generisk metod, kan relevanta generiska argument vanligtvis dras ut implicit. Om man kan dra slutsatsen om all generisk typ är det valfritt att specificera dem i syntaxen.

Tänk på följande generiska metod. Den har en formell parameter och en generisk typparameter. Det finns ett mycket uppenbart samband mellan dem - den typ som skickas som ett argument till den generiska typparametern måste vara densamma som kompileringstiden för argumentet som skickas till den formella parametern.

void M<T>(T obj)
{
}

Dessa två samtal är likvärdiga:

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

Dessa två samtal motsvarar också:

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

Och så är dessa tre samtal:

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

Observera att om åtminstone ett typargument inte kan dras, måste alla anges.

Tänk på följande generiska metod. Det första generiska argumentet är samma som typen av det formella argumentet. Men det finns inget sådant förhållande för det andra generiska argumentet. Därför har kompilatorn inget sätt att dra slutsatsen för det andra generiska argumentet i något samtal till denna metod.

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

Det här fungerar inte längre:

X("");

Detta fungerar inte heller, eftersom kompilatorn inte är säker på om vi anger den första eller den andra generiska parametern (båda skulle vara giltiga som object ):

X<object>("");

Vi måste skriva ut båda, så här:

X<string, object>("");

Skriv begränsningar (klasser och gränssnitt)

Typbegränsningar kan tvinga en typparameter att implementera ett visst gränssnitt eller klass.

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

Syntax för flera begränsningar:

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

Typbegränsningar fungerar på samma sätt som arv, eftersom det är möjligt att ange flera gränssnitt som begränsningar för den generiska typen, men endast en klass:

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

En annan regel är att klassen måste läggas till som den första begränsningen och sedan gränssnitten:

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

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

Alla deklarerade begränsningar måste vara uppfyllda samtidigt för att en viss generisk inställning ska fungera. Det finns inget sätt att specificera två eller flera alternativa begränsningar.

Skriv begränsningar (klass och struktur)

Är det möjligt att ange om eller inte den typ argumentet bör vara en referens typ eller ett värde typ genom användning av respektive begränsningar class eller struct . Om dessa begränsningar används måste de definieras innan alla andra begränsningar (till exempel en överordnad typ eller new() ) kan listas.

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

Skriv begränsningar (nyckelord)

Genom att använda den new() begränsningen är det möjligt att genomföra typparametrar för att definiera en tom (standard) konstruktör.

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.

Det andra samtalet till att Create() ger kompileringstidsfel med följande meddelande:

"Bar" måste vara en icke-abstrakt typ med en offentlig parameterlös konstruktör för att använda den som parameter "T" i den generiska typen eller metoden "Factory"

Det finns ingen begränsning för en konstruktör med parametrar, endast parametrala konstruktörer stöds.

Skriv inferens (klasser)

Utvecklare kan fångas av det faktum att typinferens inte fungerar för konstruktörer:

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.

Det första sättet att skapa instans utan att specifikt ange typparametrar orsakar kompileringstidsfel som skulle säga:

Att använda den generiska typen 'Tuple <T1, T2>' kräver två typargumenter

En vanlig lösning är att lägga till en hjälpmetod i en statisk klass:

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

Reflektera över typparametrar

typeof operatören arbetar med typparametrar.

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

Parametrar med explicita typer

Det finns olika fall där du måste specificera typparametrarna för en generisk metod. I båda nedanstående fall kan kompilatorn inte dra av alla typparametrar från de angivna metodparametrarna.

Ett fall är när det inte finns några parametrar:

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

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

Det andra fallet är när en (eller flera) av typparametrarna inte ingår i metodparametrarna:

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.

Använda generisk metod med ett gränssnitt som en begränsningstyp.

Detta är ett exempel på hur man använder den generiska typen TFood in i Eat-metoden på klassen 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);
    }
}

Du kan kalla Eat-metoden så här:

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

I det här fallet om du försöker ringa:

sheep.Eat(lion);

Det kommer inte att vara möjligt eftersom objektlejonet inte implementerar gränssnittet IFood. Att försöka ringa ovanstående samtal genererar ett kompileringsfel: "Typen" Carnivore "kan inte användas som typparameter" TFood "i den generiska typen eller metoden" Animal.Eat (TFood) ". Det finns ingen implicit referensomvandling från" Rovdjur "till 'IFood'."

Covariance

När är en IEnumerable<T> en subtyp av en annan IEnumerable<T1> ? När T är en subtyp av T1 . IEnumerable är kovariant i sin T parameter, vilket innebär att IEnumerable går i samma riktning som 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>

En instans av en samvariativ generisk typ med en given typparameter är implicit konvertibel till samma generiska typ med en mindre härledd typparameter.

Det här förhållandet beror på att IEnumerable producerar T men inte konsumerar dem. Ett objekt som producerar Dog kan användas som om det producerar Animal .

Parametrar av Covariant-typ deklareras med hjälp av out nyckelordet, eftersom parametern endast måste användas som en utgång .

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

En typparameter som deklareras som kovariant kanske inte visas som ingång.

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

Här är ett komplett exempel:

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

När är en IComparer<T> en subtyp av en annan IComparer<T1> ? När T1 är en subtyp av T IComparer är motstridande i sin T parameter, vilket innebär att IComparer går i motsatt riktning som T : er.

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>

Ett exempel på en kontroversiell generisk typ med en given typparameter är implicit konvertibel till samma generiska typ med en mer härledd typparameter.

Det här förhållandet beror på att IComparer konsumerar T men inte producerar dem. Ett objekt som kan jämföra två Animal kan användas för att jämföra två Dog .

Kontravari typparametrar förklaras med hjälp av in sökord, eftersom parametern får endast användas som en ingång.

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

En typparameter som deklareras som contravariant kanske inte visas som en utgång.

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

Invariance

IList<T> är aldrig en subtyp av en annan IList<T1> . IList är invariant i sin typparameter.

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

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

Det finns inget subtypförhållande för listor eftersom du kan sätta värden i en lista och ta ut värden ur en lista.

Om IList var kovariant, skulle du kunna lägga till objekt av fel subtyp till en given lista.

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

Om IList var IList skulle du kunna extrahera värden av fel subtyp från en given lista.

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!

Invarianta typparametrar deklareras genom att utelämna både in och out sökord.

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

Variantgränssnitt

Gränssnitt kan ha varianter av parametrar.

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

men klasser och strukturer kanske inte

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

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

inte heller generiska metoddeklarationer

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

Exemplet nedan visar flera variansdeklarationer på samma gränssnitt

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>

Variant delegater

Delegater kan ha parametrar av typen 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

Detta följer av Liskovs substitutionsprincip , som bland annat säger att en metod D kan betraktas som mer härledd än en metod B om:

  • D har en lika eller mer härledd returtyp än B
  • D har lika eller mer generella motsvarande parametertyper än B

Följande uppdrag är därför alla typsäkra:

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

Varianttyper som parametrar och returvärden

Om en kovarianttyp visas som en utgång, är den innehållande typen kovariant. Att producera en producent av T är som att producera T

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

Om en contravariant typ visas som en utgång, är den innehållande typen contravariant. Att producera en konsument av T är som att konsumera T

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

Om en kovariant typ visas som en ingång, är den innehållande typen i strid med. Att konsumera en producent av T är som att konsumera T

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

Om en kontrastvariant visas som en ingång, är den innehållande typen kovariant. Att konsumera en konsument av T är som att producera T

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

Kontrollera jämställdhet mellan generiska värden.

Om logik för generisk klass eller metod kräver kontroll av jämställdhet mellan värden med generisk typ, använd EqualityComparer<TType>.Default egenskap :

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

Detta tillvägagångssätt är bättre än att bara ringa Object.Equals() metoden, eftersom kontroller implementerings default Comparer, om TBar typ redskap IEquatale<TBar> interface och om ja, samtal IEquatable<TBar>.Equals(TBar other) metod. Detta gör det möjligt att undvika boxning / avbokning av värdetyper.

Gjutning av generisk typ

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

        

    }

användningsområden:

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

Konfigurationsläsare med gjutning av generisk typ

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

    }

användningsområden:

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow