Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Tim Corey
38分51秒
C#のジェネリックスは、その導入以来、言語の不可欠な一部となっており、その仕組みがすべての開発者に完全に理解されていなくても、多くの利点を提供しています。 動画"How To Create Generics in C#, Including New Features"では、ティム・コーリーがジェネリックがなぜ重要なのか、どのようにジェネリックを作成するのかを説明し、その実用的なアプリケーションを実演しています。
この記事では、C#ジェネリックスの包括的なガイドを提供し、このトピックに関するティム・コーリーのビデオからの貴重な洞察を提供します。 型安全性、パフォーマンスの利点、実用的なアプリケーションを含むジェネリックの基礎をカバーしています。 この記事では、ジェネリック・メソッド、クラス、インターフェースの作成、およびジェネリックへの制約の適用についても説明します。 さらに、実際の使用例と、効率的で型安全なコードを書くためにジェネリックを使用することの重要性を強調します。
C#ジェネリックスは、開発者があらゆるデータ型で動作するクラス、メソッド、インターフェイス、およびコレクションを定義できるようにすることで、柔軟で再利用可能な、型安全なコードを作成するための強力な方法を提供します。 ジェネリッククラスまたはジェネリックメソッドを使用することで、開発者は、任意のデータ型を表すことができるジェネリック型パラメータ(例えば、T)を定義することができます。 これにより、コードの重複を排除し、コンパイル時に型の安全性を確保しながらコードの再利用性を向上させます。List や Dictionary<TKey, TValue> のようなジェネリックコレクションクラスは、異なるデータ型の効率的な処理を可能にし、ジェネリックインターフェースやジェネリックデリゲートによって複数の型パラメータを扱えるカスタムジェネリック型の作成を可能にします。 ジェネリックを活用することで、開発者はコードの効率を最大化し、ジェネリックでないクラスの欠点を最小化することができます。 この柔軟性により、パフォーマンスや型安全性を犠牲にすることなく、より再利用性の高いコードを作成することができます。
Tim at (0:00)は、C#でジェネリックスが広く使われていることを強調し、このトピックを紹介しています。 なぜジェネリックが不可欠なのかを説明し、ジェネリックの作成方法と効果的な使用方法を示すことを目指しています。
Timはまず、"GenericsDemoApp"という名前の新しいコンソール・アプリケーションを作成し、UIに気を取られることなくジェネリックスのデモだけに集中します。 彼は、.NET 8とVisual Studio 2022を使用してプロジェクトをセットアップします。
Tim は (2:22) に、ジェネリックの概念を説明するために List クラスを使った例を示します。 ジェネリックスは、コレクションが保持できる要素の型を指定することを可能にし、型の安全性を提供し、実行時のエラーを回避します。
List<int> numbers = new List<int> { 1, 2, 3 };
List<string> strings = new List<string> { "Tim", "Corey", "Sue" };Dim numbers As New List(Of Integer) From {1, 2, 3}
Dim strings As New List(Of String) From {"Tim", "Corey", "Sue"}Dim numbers As New List(Of Integer) From {1, 2, 3}
Dim strings As New List(Of String) From {"Tim", "Corey", "Sue"}List は、numbers リストには整数のみが追加でき、strings リストには文字列のみが追加できることを保証します。
Timは、ジェネリックスが提供する型安全の重要性を強調します。 コンパイラは設計時に型をチェックし、型の不一致を防ぎ、安全なコード実行を保証します。 また、ジェネリックスは、ボックス化やアンボックス化の必要性を回避し、より効率的なコードに導きます。
非一般的なコレクションを使用することの非効率性を示すために、TimはListを作成し、それがどのように異なるタイプのオブジェクトを保持できるかを示します。
List<object> objects = new List<object> { "Tim", 4, 3.6m };List<object> objects = new List<object> { "Tim", 4, 3.6m };Dim objects As New List(Of Object) From {"Tim", 4, 3.6D}Dim objects As New List(Of Object) From {"Tim", 4, 3.6D}非ジェネリック・コレクションを使用すると、型の不一致や、ボクシングとアンボクシングによる非効率につながる可能性があることを説明しています。
(6:15)のTimは、100万項目を追加するためにList<object>とList<int>を使用する場合のパフォーマンス比較を実行している。 彼はストップウォッチを使って、各操作の経過時間を計測しています。
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 1; i <= 1_000_000; i++)
{
objects.Add(i);
}
stopwatch.Stop();
Console.WriteLine($"List of object elapsed time: {stopwatch.ElapsedMilliseconds} ms");
stopwatch.Restart();
for (int i = 1; i <= 1_000_000; i++)
{
numbers.Add(i);
}
stopwatch.Stop();
Console.WriteLine($"List of int elapsed time: {stopwatch.ElapsedMilliseconds} ms");Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 1; i <= 1_000_000; i++)
{
objects.Add(i);
}
stopwatch.Stop();
Console.WriteLine($"List of object elapsed time: {stopwatch.ElapsedMilliseconds} ms");
stopwatch.Restart();
for (int i = 1; i <= 1_000_000; i++)
{
numbers.Add(i);
}
stopwatch.Stop();
Console.WriteLine($"List of int elapsed time: {stopwatch.ElapsedMilliseconds} ms");Dim stopwatch As New Stopwatch()
stopwatch.Start()
For i As Integer = 1 To 1_000_000
objects.Add(i)
Next
stopwatch.Stop()
Console.WriteLine($"List of object elapsed time: {stopwatch.ElapsedMilliseconds} ms")
stopwatch.Restart()
For i As Integer = 1 To 1_000_000
numbers.Add(i)
Next
stopwatch.Stop()
Console.WriteLine($"List of int elapsed time: {stopwatch.ElapsedMilliseconds} ms")Dim stopwatch As New Stopwatch()
stopwatch.Start()
For i As Integer = 1 To 1_000_000
objects.Add(i)
Next
stopwatch.Stop()
Console.WriteLine($"List of object elapsed time: {stopwatch.ElapsedMilliseconds} ms")
stopwatch.Restart()
For i As Integer = 1 To 1_000_000
numbers.Add(i)
Next
stopwatch.Stop()
Console.WriteLine($"List of int elapsed time: {stopwatch.ElapsedMilliseconds} ms")その結果、List<int>への整数の加算は、List<object>への加算よりも、ボクシングとアンボクシングがない分、大幅に高速であることが示された。

(10:14)のTimは、TypeCheckerという名前のジェネリックメソッドを作成する方法を示している。 このメソッドは、与えられた値の型をチェックしてそれを出力し、ジェネリックスの柔軟性とパワーを説明します。
public static void TypeChecker<t>(T value)
{
Console.WriteLine($"Type: {typeof(T)}, Value: {value}");
}public static void TypeChecker<t>(T value)
{
Console.WriteLine($"Type: {typeof(T)}, Value: {value}");
}Public Shared Sub TypeChecker(Of T)(value As T)
Console.WriteLine($"Type: {GetType(T)}, Value: {value}")
End SubPublic Shared Sub TypeChecker(Of T)(value As T)
Console.WriteLine($"Type: {GetType(T)}, Value: {value}")
End SubTypeChecker メソッドは、typeof 演算子を使用してジェネリック・パラメータ T の型を決定し、型と値の両方を出力します。
Timは、さまざまなタイプの引数でTypeCheckerメソッドを呼び出す例を示している。
TypeChecker(1); // Type: System.Int32, Value: 1
TypeChecker("Tim"); // Type: System.String, Value: Tim
TypeChecker(1.1); // Type: System.Double, Value: 1.1TypeChecker(1); // Type: System.Int32, Value: 1
TypeChecker("Tim"); // Type: System.String, Value: Tim
TypeChecker(1.1); // Type: System.Double, Value: 1.1TypeChecker(1) ' Type: System.Int32, Value: 1
TypeChecker("Tim") ' Type: System.String, Value: Tim
TypeChecker(1.1) ' Type: System.Double, Value: 1.1TypeChecker(1) ' Type: System.Int32, Value: 1
TypeChecker("Tim") ' Type: System.String, Value: Tim
TypeChecker(1.1) ' Type: System.Double, Value: 1.1TypeCheckerメソッドに様々な型を渡すことで、Timはジェネリックがいかにシームレスに異なるデータ型を扱えるかを示している。
Timは(16:25)で、BetterListという名前のジェネリック・クラスの作成に移る。このクラスは指定された型のリストをカプセル化し、追加機能を提供する。
public class BetterList<t>
{
private List<t> data = new List<t>();
public void AddToList(T value)
{
data.Add(value);
Console.WriteLine($"{value} has been added to the list");
}
}public class BetterList<t>
{
private List<t> data = new List<t>();
public void AddToList(T value)
{
data.Add(value);
Console.WriteLine($"{value} has been added to the list");
}
}Public Class BetterList(Of t)
Private data As New List(Of t)()
Public Sub AddToList(value As t)
data.Add(value)
Console.WriteLine($"{value} has been added to the list")
End Sub
End ClassPublic Class BetterList(Of t)
Private data As New List(Of t)()
Public Sub AddToList(value As t)
data.Add(value)
Console.WriteLine($"{value} has been added to the list")
End Sub
End ClassBetterList クラスには、プライベートな List とリストに値を追加し、追加を示すメッセージを表示するメソッド AddToList があります。
Timは、BetterListクラスをさまざまな型で使用する例を示しています。
BetterList<int> betterNumbers = new BetterList<int>();
betterNumbers.AddToList(5);
BetterList<PersonRecord> people = new BetterList<PersonRecord>();
people.AddToList(new PersonRecord("Tim", "Corey"));BetterList<int> betterNumbers = new BetterList<int>();
betterNumbers.AddToList(5);
BetterList<PersonRecord> people = new BetterList<PersonRecord>();
people.AddToList(new PersonRecord("Tim", "Corey"));Dim betterNumbers As New BetterList(Of Integer)()
betterNumbers.AddToList(5)
Dim people As New BetterList(Of PersonRecord)()
people.AddToList(New PersonRecord("Tim", "Corey"))Dim betterNumbers As New BetterList(Of Integer)()
betterNumbers.AddToList(5)
Dim people As New BetterList(Of PersonRecord)()
people.AddToList(New PersonRecord("Tim", "Corey"))これらの例では、BetterList<int> は整数のリストを管理し、BetterList<PersonRecord> は PersonRecord オブジェクトのリストを管理します。 リストに追加されるたびに、付加価値を示すコンソールメッセージが表示されます。
Timは(21:48)で、IImportanceという名前の汎用インターフェースのアイデアを紹介している。 このインタフェースは、2つの値のどちらが重要かを決定する方法を定義します。
public interface IImportance<t>
{
T MostImportant(T a, T b);
}public interface IImportance<t>
{
T MostImportant(T a, T b);
}Public Interface IImportance(Of T)
Function MostImportant(a As T, b As T) As T
End InterfacePublic Interface IImportance(Of T)
Function MostImportant(a As T, b As T) As T
End InterfaceTimは、さまざまな型に対してこのインターフェイスを実装する方法を示します。彼は整数の実装から始めます。
public class EvaluateImportance : IImportance<int>
{
public int MostImportant(int a, int b)
{
return a > b ? a : b;
}
}public class EvaluateImportance : IImportance<int>
{
public int MostImportant(int a, int b)
{
return a > b ? a : b;
}
}Public Class EvaluateImportance
Implements IImportance(Of Integer)
Public Function MostImportant(a As Integer, b As Integer) As Integer Implements IImportance(Of Integer).MostImportant
Return If(a > b, a, b)
End Function
End ClassPublic Class EvaluateImportance
Implements IImportance(Of Integer)
Public Function MostImportant(a As Integer, b As Integer) As Integer Implements IImportance(Of Integer).MostImportant
Return If(a > b, a, b)
End Function
End Class次に、文字列のインターフェイスを実装し、文字列の長さを使って重要度を決定する。
public class EvaluateStringImportance : IImportance<string>
{
public string MostImportant(string a, string b)
{
return a.Length > b.Length ? a : b;
}
}public class EvaluateStringImportance : IImportance<string>
{
public string MostImportant(string a, string b)
{
return a.Length > b.Length ? a : b;
}
}Public Class EvaluateStringImportance
Implements IImportance(Of String)
Public Function MostImportant(a As String, b As String) As String Implements IImportance(Of String).MostImportant
Return If(a.Length > b.Length, a, b)
End Function
End ClassPublic Class EvaluateStringImportance
Implements IImportance(Of String)
Public Function MostImportant(a As String, b As String) As String Implements IImportance(Of String).MostImportant
Return If(a.Length > b.Length, a, b)
End Function
End Classこれらの実装は、同じインターフェイスを、それぞれのタイプに固有のロジックを持つ異なるタイプにどのように適用できるかを示しています。
(25:21)のTimは、ジェネリックスに制約を適用する方法を説明し、ジェネリックスが特定の条件を満たすことを保証します。 例えば、ジェネリック型は、空のコンストラクタを持つことや、特定のインターフェースを実装することを制約することができます。
public class SampleClass<t> where T : new()
{
// Class implementation
}
public class SampleClassWithInterface<t> where T : IImportance<t>
{
// Class implementation
}public class SampleClass<t> where T : new()
{
// Class implementation
}
public class SampleClassWithInterface<t> where T : IImportance<t>
{
// Class implementation
}Public Class SampleClass(Of T As {New})
' Class implementation
End Class
Public Class SampleClassWithInterface(Of T As IImportance(Of T))
' Class implementation
End ClassPublic Class SampleClass(Of T As {New})
' Class implementation
End Class
Public Class SampleClassWithInterface(Of T As IImportance(Of T))
' Class implementation
End Classこれらの制約は、汎用型が必要な基準を満たしていることを保証し、実行時のエラーを防ぎ、型の安全性を高めるのに役立ちます。
Timは、MicrosoftがINumberインタフェースを使用して数値演算を制約する方法について説明します。 これは、汎用型に対する加算や減算などの算術演算を可能にします。
public class MathOperations<t> where T : INumber<t>
{
public T Add(T x, T y)
{
return x + y;
}
}public class MathOperations<t> where T : INumber<t>
{
public T Add(T x, T y)
{
return x + y;
}
}Public Class MathOperations(Of T As {INumber(Of T)})
Public Function Add(x As T, y As T) As T
Return x + y
End Function
End ClassPublic Class MathOperations(Of T As {INumber(Of T)})
Public Function Add(x As T, y As T) As T
Return x + y
End Function
End Classジェネリック型 T を INumber に制約することで、その型が数値演算をサポートすることを保証します。
Timは(33:55)でMathOperationsクラスについて説明し、ジェネリックスが倍数や小数などの異なる数値型でどのように使用できるかを示します。
Timは、整数と倍数のMathOperationsのインスタンスを作成する方法を示しています:
MathOperations<int> intMath = new MathOperations<int>();
Console.WriteLine(intMath.Add(1, 4)); // Outputs: 5
MathOperations<double> doubleMath = new MathOperations<double>();
Console.WriteLine(doubleMath.Add(1.5, 4.3)); // Outputs: 5.8MathOperations<int> intMath = new MathOperations<int>();
Console.WriteLine(intMath.Add(1, 4)); // Outputs: 5
MathOperations<double> doubleMath = new MathOperations<double>();
Console.WriteLine(doubleMath.Add(1.5, 4.3)); // Outputs: 5.8Dim intMath As New MathOperations(Of Integer)()
Console.WriteLine(intMath.Add(1, 4)) ' Outputs: 5
Dim doubleMath As New MathOperations(Of Double)()
Console.WriteLine(doubleMath.Add(1.5, 4.3)) ' Outputs: 5.8Dim intMath As New MathOperations(Of Integer)()
Console.WriteLine(intMath.Add(1, 4)) ' Outputs: 5
Dim doubleMath As New MathOperations(Of Double)()
Console.WriteLine(doubleMath.Add(1.5, 4.3)) ' Outputs: 5.8これは、ジェネリックスの柔軟性を示すもので、異なる数値型を同じクラス内でシームレスに扱うことができます。
ティムは、異なる数値型を混ぜてはいけないことを示すことで、型安全の重要性を強調している。例えば、doubleをintegerに加えようとすると、コンパイル時にエラーになる。
// This will result in a compile-time error
// Console.WriteLine(intMath.Add(1.5, 4));// This will result in a compile-time error
// Console.WriteLine(intMath.Add(1.5, 4));' This will result in a compile-time error
' Console.WriteLine(intMath.Add(1.5, 4))' This will result in a compile-time error
' Console.WriteLine(intMath.Add(1.5, 4))Timは、型変換に伴うオーバーヘッドを避けるためにジェネリックスを使用する利点について説明します。 例えば、整数を倍数に変換して数学演算を行い、再び整数に戻すにはコストがかかります。 ジェネリックスを使用することで、ネイティブの型に対する直接操作が可能になり、パフォーマンスと精度が保たれます。
Timは、ジェネリックを使用する際には注意するようアドバイスし、開発者がジェネリックを適切に使用し、使い過ぎないようにすることを推奨しています。 ジェネリックスの利点である、型安全性、ボックス化とアンボックス化の削減、コンパイル時のチェック、コードの可読性の向上などを強調しています。
彼はまた、ジェネリックが List や Dictionary<TKey, TValue> のようなコレクションや、事前に特定の詳細を知る必要なく様々な型を処理できるロギングフレームワークで普及していることを指摘します。
Tim CoreyによるC#における高度なジェネリックの詳細な探求は、その実用的なアプリケーションと利点に関する貴重な洞察を提供します。 ジェネリックスの理解を深め、実際の例を見たい場合は、C# Generics に関する Tim Corey の詳細なビデオをぜひご覧ください。 明確な説明と実践的なデモンストレーションにより、コンセプトを完全に把握し、自身のプロジェクトに効果的に適用することができます。
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Join our newsletter, you’ll get exclusive access on article updates. We value your privacy