C# Language
ジェネリックス
サーチ…
構文
-
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; } }
パラメーター
パラメーター) | 説明 |
---|---|
T、V | ジェネリック宣言のプレースホルダの入力 |
備考
彼らの一般的なセマンティクスも後にコンパイルされ、保存されますのC#で構築されたジェネリック型:C#でジェネリックがダウンし、実行時にすべての方法はサポートされているCILを 。
これは、事実上、C#ではジェネリック型を反映し、宣言されたときにオブジェクトを表示したり、オブジェクトがジェネリック型のインスタンスであるかどうかをチェックすることができます。これは、コンパイル時にジェネリック型の情報が削除される型消去とは対照的です。これは、実行時に複数の具体的なジェネリック型が複数の非ジェネリック型になり、元のジェネリック型の定義をさらにインスタンス化するために必要なメタデータが失われるジェネリックのテンプレートアプローチとは対照的です。
しかし、ジェネリック型に反映するときは注意が必要です。ジェネリック型の名前はコンパイル時に変更され、角括弧と型パラメータの名前をバックティックとジェネリック型パラメータの数で置き換えます。したがって、 Dictionary<TKey, Tvalue>
はDictionary`2
に変換される。
型パラメータ(クラス)
宣言:
class MyGenericClass<T1, T2, T3, ...>
{
// Do something with the type parameters.
}
初期化:
var x = new MyGenericClass<int, char, bool>();
使用法(パラメータの型として):
void AnotherMethod(MyGenericClass<float, byte, char> arg) { ... }
型パラメータ(メソッド)
宣言:
void MyGenericMethod<T1, T2, T3>(T1 a, T2 b, T3 c)
{
// Do something with the type parameters.
}
呼び出し:
コンパイラが暗黙的に型を推論できるので、型引数に型引数を与える必要はありません。
int x =10;
int y =20;
string z = "test";
MyGenericMethod(x,y,z);
しかし、あいまいさがある場合は、ジェネリックメソッドをtype arguemntsで呼び出す必要があります。
MyGenericMethod<int, int, string>(x,y,z);
タイプパラメータ(インタフェース)
宣言:
interface IMyGenericInterface<T1, T2, T3, ...> { ... }
使用法(継承):
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> { ... }
使用法(パラメータの型として):
void SomeMethod(IMyGenericInterface<int, char, bool> arg) { ... }
暗黙の型推論(メソッド)
正式な引数を汎用メソッドに渡すとき、関連する汎用型の引数は、通常、暗黙的に推論できます。すべてのジェネリック型を推論できる場合は、構文で指定することはオプションです。
次の一般的な方法を考えてみましょう。 1つの仮パラメータと1つのジェネリック型パラメータがあります。それらの間には非常に明白な関係があります。ジェネリック型パラメータへの引数として渡される型は、仮パラメータに渡される引数のコンパイル時型と同じでなければなりません。
void M<T>(T obj)
{
}
これらの2つの呼び出しは同等です。
M<object>(new object());
M(new object());
これらの2つの呼び出しも同等です。
M<string>("");
M("");
これらの3つの呼び出しも同様です。
M<object>("");
M((object) "");
M("" as object);
少なくとも1つの型引数が推論できない場合は、それらのすべてを指定する必要があることに注意してください。
次の一般的な方法を考えてみましょう。最初のジェネリック型引数は、仮引数の型と同じです。しかし、第2のジェネリック型の引数にはそのような関係はありません。したがって、コンパイラは、このメソッドへの呼び出しで第2ジェネリック型の引数を推論する方法がありません。
void X<T1, T2>(T1 obj)
{
}
これはもう動作しません:
X("");
これは、どちらも機能しません。コンパイラは、第1または第2ジェネリックパラメータを指定しているかどうかわかりません(どちらもobject
として有効です)。
X<object>("");
私たちは、次のように両方をタイプする必要があります:
X<string, object>("");
型の制約(クラスとインタフェース)
型制約は、型パラメータが特定のインタフェースまたはクラスを実装するように強制することができます。
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());
複数の制約の構文:
class Generic<T, T1>
where T : IType
where T1 : Base, new()
{
}
型制約は、継承と同じ方法で動作します。つまり、汎用型の制約として複数のインタフェースを指定できますが、クラスは1つのみです。
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
{
}
もう1つのルールは、クラスを最初の制約として追加し、次にインターフェイスを追加する必要があるということです。
class Generic<T>
where T : A, I1
{
}
class Generic2<T>
where T : I1, A //Compilation error
{
}
特定の汎用インスタンス化が機能するためには、宣言された制約はすべて同時に満たされなければなりません。 2つ以上の代替セットを指定する方法はありません。
型制約(クラスと構造体)
それぞれの制約class
またはstruct
を使用して、型引数を参照型または値型にする必要があるかどうかを指定することができます。これらの制約が使用されている場合は、他のすべての制約(たとえば親の型またはnew()
)をリストする前に定義する 必要があります。
// 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
{
}
}
タイプ制約(new-keyword)
new()
制約を使用することによって、空の(デフォルトの)コンストラクタを定義する型パラメータを強制することができます。
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.
Create()
への2回目の呼び出しでは、次のメッセージとともにコンパイル時エラーが発生します。
'Bar'は、ジェネリック型またはメソッド 'Factory'でパラメーター 'T'として使用するために、パブリックパラメーターなしコンストラクターを持つ非抽象型でなければなりません
パラメータを持つコンストラクタの制約はなく、パラメータのないコンストラクタのみがサポートされています。
型推論(クラス)
開発者は、型推論がコンストラクタでは機能しないという事実から逃れることができます:
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.
型パラメータを明示的に指定せずにインスタンスを作成する最初の方法は、次のようなコンパイル時エラーを引き起こします。
ジェネリック型 'Tuple <T1、T2>'を使うには2つの型引数が必要です
一般的な回避策は、静的クラスにヘルパーメソッドを追加することです。
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...
型パラメータを反映
typeof
演算子は型パラメータに作用します。
class NameGetter<T>
{
public string GetTypeName()
{
return typeof(T).Name;
}
}
明示的な型パラメータ
ジェネリックメソッドの型パラメータを明示的に指定する必要がある場合があります。以下の両方の場合、コンパイラは、指定されたメソッドパラメータからすべての型パラメータを推論することができません。
1つのケースは、パラメータがない場合です。
public void SomeMethod<T, V>()
{
// No code for simplicity
}
SomeMethod(); // doesn't compile
SomeMethod<int, bool>(); // compiles
第2のケースは、型パラメータの1つ(またはそれ以上)がメソッドパラメータの一部ではない場合です。
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.
インタフェースを制約タイプとして使用する汎用メソッドの使用。
これは、クラスAnimalのEatメソッド内でジェネリック型のTFoodを使用する方法の例です
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);
}
}
Eatメソッドは次のように呼び出すことができます:
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
この場合、電話をしようとすると:
sheep.Eat(lion);
オブジェクトライオンがインタフェースIFoodを実装していないため、これは不可能です。上記の呼び出しを試みるとコンパイラエラーが発生します: "型 'Carnivore'は、汎用型またはメソッド 'Animal.Eat(TFood)'の型パラメータ 'TFood'として使用できません ' 「Carnivore」から「IFood」まで。
共分散
IEnumerable<T>
が別のIEnumerable<T1>
サブタイプであるのはいつですか? T
がT1
サブタイプである場合。 IEnumerable
はそのT
パラメータで共変です。つまり、 IEnumerable
のサブタイプの関係は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>
与えられた型パラメータを持つ共変ジェネリック型のインスタンスは、派生型パラメータが少ない同じジェネリック型に暗黙的に変換可能です。
IEnumerable
は T
sを生成するが、それらを消費しないため、この関係が成り立つ。 Dog
を生成するオブジェクトは、 Animal
生成する場合と同様に使用できます。
共変タイプのパラメーターは、 出力としてのみ使用する必要があるため、 out
キーワードを使用しout
宣言されます 。
interface IEnumerable<out T> { /* ... */ }
共変量として宣言された型パラメータは、入力としては現れません。
interface Bad<out T>
{
void SetT(T t); // type error
}
ここに完全な例があります:
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
}
}
}
コントラスト変動
IComparer<T>
が異なるIComparer<T1>
サブタイプであるときはいつですか? T1
がT
サブタイプである場合。 IComparer
はそのT
パラメータで反変です。つまり、 IComparer
のサブタイプの関係は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>
与えられた型パラメータを持つ反変ジェネリック型のインスタンスは、より派生型のパラメータを持つ同じジェネリック型に暗黙的に変換可能です。
この関係は、 IComparer
T
sを消費するが、それらを生成しないために保持される。任意の2つのAnimal
を比較できるオブジェクトを使用して、2つのDog
を比較することができます。
contravariant型のパラメータは、 in
キーワードを使用して宣言されます 。これは、パラメータが入力としてのみ使用される必要があるためです。
interface IComparer<in T> { /* ... */ }
反例として宣言された型パラメータは、出力として現れないかもしれません。
interface Bad<in T>
{
T GetT(); // type error
}
不変性
IList<T>
は決して異なるIList<T1>
サブタイプではありません。 IList
は型パラメーターで不変です。
class Animal { /* ... */ }
class Dog : Animal { /* ... */ }
IList<Dog> dogs = new List<Dog>();
IList<Animal> animals = dogs; // type error
あなたがリストに値を入れて、リストから値を取ることができますので、リストにはサブタイプの関係はありません。
IList
が共IList
である場合、 間違ったサブタイプの項目を指定のリストに追加することができます。
IList<Animal> animals = new List<Dog>(); // supposing this were allowed...
animals.Add(new Giraffe()); // ... then this would also be allowed, which is bad!
IList
が反反例である場合、与えられたリストから間違ったサブタイプの値を抽出することができます。
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!
不変型パラメータは、 in
とout
キーワードの両方を省略することによって宣言されます。
interface IList<T> { /* ... */ }
バリアントインタフェース
インタフェースには型パラメータがあります。
interface IEnumerable<out T>
{
// ...
}
interface IComparer<in T>
{
// ...
}
クラスと構造は
class BadClass<in T1, out T2> // not allowed
{
}
struct BadStruct<in T1, out T2> // not allowed
{
}
ジェネリックメソッド宣言もしない
class MyClass
{
public T Bad<out T, in T1>(T1 t1) // not allowed
{
// ...
}
}
以下の例は、同じインタフェース上の複数の分散宣言を示しています
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>
異人代表者
代理人は、バリアント型パラメータを持つことができます。
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
これは、 Liskov Substitution Principle ( Liskov Substitution Principle )に基づいています。これは、(特に)方法Dは、以下の場合にメソッドBよりも導かれたと考えることができると述べています。
- DはBと等しいまたはより多くの派生型を返します。
- Dは、Bと同等以上の一般的な対応するパラメータタイプを持つ
したがって、以下の割り当てはすべて型安全です。
Func<object, string> original = SomeMethod;
Func<object, object> d1 = original;
Func<string, string> d2 = original;
Func<string, object> d3 = original;
パラメータと戻り値としてのバリアント型
共変型が出力として出現する場合、その包含型は共変である。プロデューサー産生T
Sは製造のようなものであるT
秒。
interface IReturnCovariant<out T>
{
IEnumerable<T> GetTs();
}
contravariant型が出力として現れる場合、包含型は反変です。消費者生産T
SはかかるようであるT
秒。
interface IReturnContravariant<in T>
{
IComparer<T> GetTComparer();
}
共変型が入力として出現する場合、包含する型は反変的である。プロデューサー消費T
SはかかるようであるT
秒。
interface IAcceptCovariant<in T>
{
void ProcessTs(IEnumerable<T> ts);
}
反変的型が入力として現れる場合、包含型は共変である。消費者消費T
Sは、生産のようであるT
秒。
interface IAcceptContravariant<out T>
{
void CompareTs(IComparer<T> tComparer);
}
一般的な値の等価性のチェック
ジェネリッククラスまたはメソッドのロジックが、ジェネリック型を持つ値の等価性をチェックする必要がある場合は、 EqualityComparer<TType>.Default
プロパティを使用します 。
public void Foo<TBar>(TBar arg1, TBar arg2)
{
var comparer = EqualityComparer<TBar>.Default;
if (comparer.Equals(arg1,arg2)
{
...
}
}
TBar
型がIEquatale<TBar>
インタフェースを実装しているかどうかをチェックし、そうであればIEquatable<TBar>.Equals(TBar other)
メソッドを呼び出すため、このアプローチは単純にObject.Equals()
メソッドを呼び出すよりも優れています。これにより、値の種類のボクシング/ボックス解除を回避できます。
汎用鋳造
/// <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));
}
}
用途:
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);
ジェネリック型キャスティングの設定リーダー
/// <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));
}
}
用途:
var timeOut = ConfigurationReader.GetConfigKeyValue("RequestTimeout", 2000);
var url = ConfigurationReader.GetConfigKeyValue("URL", "www.someurl.com");
var enabled = ConfigurationReader.GetConfigKeyValue("IsEnabled", false);