「C Sharpの基礎 - 拡張メソッド」の版間の差分
(→構文) |
|||
26行目: | 26行目: | ||
<u>ただし、制約の選択には慎重を期し、設計の目的と要件に基づいて適切に判断することが重要である。</u><br> | <u>ただし、制約の選択には慎重を期し、設計の目的と要件に基づいて適切に判断することが重要である。</u><br> | ||
<br> | <br> | ||
==== | ==== シンタックス ==== | ||
制約には複数の種類があり、カンマ区切りで複数指定することができる。<br> | |||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
where <型パラメータ> : <制約1>, <制約2>, <制約3>, ... | where <型パラメータ> : <制約1>, <制約2>, <制約3>, ... | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<br> | <br> | ||
==== 主な制約の種類 ==== | ==== 主な制約の種類 ==== | ||
* クラス制約 (where T : <クラス名またはサブクラス名>) | * クラス制約 (where T : <クラス名またはサブクラス名>) |
2024年8月25日 (日) 14:39時点における版
概要
C#において、独自の拡張メソッドを実装する手順を記載する。
拡張メソッドを使用するには、usingディレクティブを使用して、拡張メソッドが定義されている名前空間を指定する。
拡張メソッドの実装
- 拡張メソッドを実装するための静的クラスを定義する。
この静的クラスは、クライアントコードから参照できる必要がある。 - 拡張メソッドを静的メソッドとして実装する。
- メソッドの最初の引数では、操作する型を指定する。
型名の前には、this修飾子を付加する。
名前の衝突において、呼び出し元のクラスで定義されているメソッドが優先されるため、拡張メソッドが使用されることはない。
制約
制約とは
制約 (Constraints) とは、ジェネリック型パラメータに対して特定の条件を課すものである。
これにより、型の安全性を高め、ソースコード内でその型パラメータの特定の機能を使用できるようになる。
※注意
過度に制約を課すと、ジェネリックの柔軟性が失われる可能性がある。
制約は必要最小限にとどめ、コードの再利用性とのバランスを取ることが重要である。
一部の制約の組み合わせは論理的に矛盾する場合がある (例: where T : struct, new()
)
制約を適切に使用することで、型安全性の高いより表現力豊かなジェネリックコードを記述することができる。
ただし、制約の選択には慎重を期し、設計の目的と要件に基づいて適切に判断することが重要である。
シンタックス
制約には複数の種類があり、カンマ区切りで複数指定することができる。
where <型パラメータ> : <制約1>, <制約2>, <制約3>, ...
主な制約の種類
- クラス制約 (where T : <クラス名またはサブクラス名>)
- Tは指定されたクラスまたはそのサブクラスである必要がある。
- 例: where T : Animal
- インターフェース制約 (where T : <インターフェース名>)
- Tは指定されたインターフェースを実装している必要がある。
- 例: where T : IComparable<T>
- 構造体 (値型) 制約 (where T : struct)
- Tは値型である必要がある。
- int型、float型、double型、struct型等が該当する。
- クラス (参照型) 制約 (where T : class)
- Tは参照型である必要がある。
- クラス、インターフェース、デリゲート、配列が該当する。
- 新しいインスタンス制約 (where T : new())
- Tはパラメータ無しのパブリックコンストラクタを持つ必要がある。
- 非Null制約 (where T : notnull)
C# 8.0以降- Tはnull非許容型である必要がある。
- アンマネージド制約 (where T : unmanaged)
- Tはアンマネージド型 (ポインタ、列挙型、構造体等) である必要がある。
- 他の型パラメータとの関係を示す制約
- 例: where T : U
- ただし、TはUまたはUのサブクラスである必要がある。
複数の制約の組み合わせ
複数の制約を組み合わせることにより、より具体的な要件を指定できる。
以下の例では、Tは参照型、IComparable<T>を実装、パラメータ無しのコンストラクタを持つ必要がある。
where T : class, IComparable<T>, new()
制約のメリット
- 型の安全性
- コンパイル時に型チェックが行われ、不適切な型の使用を防ぐ。
- パラメータ特性
- 型パラメータの特性が明確になるため、ジェネリッククラスやメソッドでその型の特定の機能を使用できるようになる。
- IntelliSenseのサポート
- 制約に基づいて、使用可能なメンバが提案される。
- パフォーマンスの向上
- コンパイラが型情報を活用して最適化を行うことができる。
- コードの明確性
- 型パラメータの要件が明確になり、コードの意図が理解しやすくなる。
制約の使用例
以下の例では、TはIComparable<T>インターフェースを実装して、かつ、パラメータ無しのコンストラクタを持つ必要がある。
public class DataProcessor<T>
where T : IComparable<T>, new()
{
public void ProcessData(T data)
{
T defaultValue = new T(); // new()制約があるため可能
if (data.CompareTo(defaultValue) > 0) // IComparable<T>制約があるため可能
{
// 何らかの処理
}
}
}
以下の例では、Tは参照型、IComparable<T>インターフェースを実装、パラメータ無しのコンストラクタを持つ必要があり、
また、Uは値型でなければならない。
public class GenericClass<T, U>
where T : class, IComparable<T>, new()
where U : struct
{
// クラスの実装
}
拡張メソッドの使用
- 呼び出し元において、usingディレクティブを使用して、拡張メソッドの静的クラスを含む名前空間を指定する。
- クラスのインスタンスメソッドと同様にメソッドを記述する。
呼び出し元では、最初の引数は指定しない。
これは、演算子を適用する型を表すものであり、コンパイラはオブジェクトの型を既に認識しているためである。
指定する必要があるのは、第2引数以降の引数のみである。
静的クラスに静的メソッドを定義して、その第1引数の前にthis
キーワードを付加する時、拡張メソッドになる。
第1引数の型が、拡張される対象となる。
整数型
以下の例では、拡張メソッドAddは、int型を拡張している。(第1引数のthis int)
拡張メソッドに第2引数を指定する時、拡張メソッドの第1引数として渡される。
つまり、拡張メソッドに引数を付加して呼び出す場合、その引数は拡張メソッドの第2引数以降として渡される。
using System;
namespace SampleNamespace
{
public static class SampleExtension
{
public static int Add(this int m, int n) => m + n;
}
}
using System;
using SampleNamespace; // 拡張メソッドを定義している名前空間
class Program
{
static void Main(string[] args)
{
int m = 2;
int result = m.Add(3);
// また、通常の静的メソッドとして、SampleExtension.Add(m, 3)のように呼び出すことも可能である
// SampleExtension.Add(m, 3)
// 定数リテラルでも拡張メソッドを使用することができる
// int result = 2.Add(3); 出力 : 5
Console.WriteLine($"m.Add(3) : {result}"); // 出力 : 5
}
}
浮動小数点型
以下の例では、拡張メソッドAddは、浮動小数点型を拡張している。(第1引数のthis double)
拡張メソッドに第2引数を指定する時、拡張メソッドの第1引数として渡される。
つまり、拡張メソッドに引数を付加して呼び出す場合、その引数は拡張メソッドの第2引数以降として渡される。
using System;
namespace FloatingPointExtensions
{
public static class FloatingPointExtension
{
public static double Add(this double m, double n) => m + n;
public static float Add(this float m, float n) => m + n;
// オーバーロードを追加して、int型の引数も受け付ける場合
public static double Add(this double m, int n) => m + n;
public static float Add(this float m, int n) => m + n;
// ジェネリックを使用して型パラメータを導入する場合
// これにより、様々な数値型に対応する
public static T Add<T, U>(this T m, U n) where T : struct, IConvertible
where U : struct, IConvertible
{
dynamic a = m;
dynamic b = n;
return a + b;
}
}
}
ジェネリックを使用して型パラメータを導入する場合、
上記のサンプルコードにおいて、public static T Add<T, U>(this T m, U n)では、2つの型パラメータTとUを使用している。
これにより、メソッドは様々な数値型の組み合わせに対応することができる。
- 制約
where <型パラメータ> : <制約 1>, <制約 2>, <制約 3>, ...
- where T : struct, IConvertible
- where U : struct, IConvertible
- これらの制約により、TとUは値型 (struct) であり、かつ、IConvertibleインターフェースを実装している必要がある。
- これは、ほとんどの数値型 (int, float, double等) に当てはまる。
dynamic
キーワード- dynamic a = m;
- dynamic b = n;
dynamic
キーワードを使用することにより、コンパイル時ではなく実行時に型チェックが行われる。- これにより、異なる型同士の加算を可能にしている。
- 戻り値
- return a + b;
- 動的に型が決定された変数aとbを加算して、その結果を返す。
- 戻り値の型は、Tになる。
このアプローチのメリットは、1つのメソッドで多くの数値型の組み合わせに対応できることである。
例えば、float型とint型、double型とlong型等の組み合わせでも使用することができる。
ただし、このアプローチにはいくつかの注意点がある。
dynamic
キーワードの使用により、型安全性が部分的に失われる。- パフォーマンスが若干低下する可能性がある。
- オーバーフローのチェックが行われないため、大きな数値を扱う場合は注意が必要である。
文字列型
以下の例では、CustomExtensions名前空間のStringExtensionクラスを作成して、WordCount拡張メソッドを実装している。
この拡張メソッドは、第1引数で指定したStringクラスを操作する。
using System.Linq;
using System.Text;
using System;
namespace CustomExtensions
{
// Extension methods must be defined in a static class.
public static class StringExtension
{
// This is the extension method.
// The first parameter takes the "this" modifier
// and specifies the type for which the method is defined.
public static int WordCount(this String str)
{
return str.Split(new char[] {' ', '.','?'}, StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}
// Import the extension method namespace.
using CustomExtensions;
namespace Extension_Methods_Simple
{
class Program
{
static void Main(string[] args)
{
string s = "The quick brown fox jumped over the lazy dog.";
// Call the method as if it were an
// instance method on the type. Note that the first
// parameter is not specified by the calling code.
int i = s.WordCount();
System.Console.WriteLine("Word count of s is {0}", i);
}
}
}
インタフェースおよびジェネリック
インタフェースおよびジェネリックに対しても拡張メソッドも作成することができる。
C#のインタフェースはデフォルトの実装を持つことはできないが、それに近いことが可能である。
public static bool IsGreaterThan<T>(this IComparable<T> m, T n) where T : struct => (0 < m.CompareTo(n));
// 使用例
Console.WriteLine($"{2.IsGreaterThan(3)}"); // 出力:False
Console.WriteLine($"{2.5.IsGreaterThan(2.5)}"); // 出力:False
Console.WriteLine($"{2.7d.IsGreaterThan(2.5d)}"); // 出力:True
Console.WriteLine($"{(new DateTime(2017, 11, 15)).IsGreaterThan(new DateTime(2017, 11, 14))}");
ジェネリックメソッド
ジェネリックメソッドとは、型パラメータを使用して定義されるメソッドのことである。
これにより、同じロジックを異なる型に対して適用できる柔軟性の高い設計を行うことができる。
ジェネリックメソッドを使用することにより、型に依存しない汎用的な設計を行うことができ、ソースコードの再利用性と保守性が向上する。
ただし、ジェネリックメソッドが適切でない場合もあるため、使用する状況を慎重に検討する必要がある。
ジェネリックメソッドのメリットを以下に示す。
- ソースコードの再利用性が向上する。
- 型安全性を維持しながら、汎用的なソースコードを記述することができる。
- パフォーマンスが向上する可能性がある。(ボックス化 / アンボックス化の回避)
ジェネリックメソッドの作成手順を以下に示す。
- メソッド宣言
- 通常のメソッド宣言に型パラメータを追加する。
public static T MethodName<T>(T parameter)
- 型パラメータの指定
- メソッド名の後にブラケット
<>
を使用して、その中に型パラメータを指定する。 <T>
または<T, U>
等
- メソッド名の後にブラケット
- 必要に応じて制約を追加
- 型パラメータに制約を加えるには、
where
キーワードを使用する。
- 型パラメータに制約を加えるには、
public static T MethodName<T>(T parameter) where T : struct
- メソッド本体の実装
- 型パラメータを使用してメソッドのロジックを実装する。
※注意
型パラメータは慣習的に大文字のTから始まる。(T, U, V, ....)
複雑な制約を設定する場合は、可読性に注意すること。
以下の例では、2つの値を交換するジェネリックメソッドを作成している。
public static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
// 使用例
int x = 5,
y = 10;
Swap(ref x, ref y);
Console.WriteLine($"x: {x}, y: {y}"); // 出力: x: 10, y: 5
string s1 = "Hello",
s2 = "World";
Swap(ref s1, ref s2);
Console.WriteLine($"s1: {s1}, s2: {s2}"); // 出力: s1: World, s2: Hello
メソッドチェーン
メソッドチェーンとは、メソッドを鎖のように続けて記述することである。
例えば、LINQでは <プロパティ名>.Where(/* 処理 1 */).Select(/* 処理 2 */) のようにメソッドを繋げて記述することができる。
以下の例では、拡張メソッドAddと拡張メソッドMultiplyを定義する場合、メソッドチェーンが使用できる。
// 拡張メソッド
public static int Add(this int m, int n) => m + n;
public static int Multiply(this int m, int n) => m * n;
// 使用例
int result = 2.Add(3).Multiply(4);
Console.WriteLine($"2.Add(3).Multiply(4)→{result}"); // 出力 : 20