「C Sharpの基礎 - 拡張メソッド」の版間の差分

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
ナビゲーションに移動 検索に移動
93行目: 93行目:
  }
  }
  </syntaxhighlight>
  </syntaxhighlight>
<br>
ジェネリックを使用して型パラメータを導入する場合、<br>
上記のサンプルコードにおいて、<u>public static T Add<T, U>(this T m, U n)</u>では、2つの型パラメータTとUを使用している。<br>
これにより、メソッドは様々な数値型の組み合わせに対応することができる。<br>
<br>
* 制約
*: <code>where <型パラメータ> : <制約 1>, <制約 2>, <制約 3>, ...</code>
*: <br>
*: <u>where T : struct, IConvertible</u>
*: <u>where U : struct, IConvertible</u>
*: <br>
*: これらの制約により、TとUは値型 (struct) であり、かつ、IConvertibleインターフェースを実装している必要がある。
*: これは、ほとんどの数値型 (int, float, double等) に当てはまる。
*: <br>
* <code>dynamic</code>キーワード
*: dynamic a = m;
*: dynamic b = n;
*: <code>dynamic</code>キーワードを使用することにより、コンパイル時ではなく実行時に型チェックが行われる。
*: これにより、異なる型同士の加算を可能にしている。
*: <br>
* 戻り値
*: return a + b;
*: 動的に型が決定された変数aとbを加算して、その結果を返す。
*: 戻り値の型は、Tになる。
<br>
このアプローチのメリットは、1つのメソッドで多くの数値型の組み合わせに対応できることである。<br>
例えば、float型とint型、double型とlong型等の組み合わせでも使用することができる。<br>
<br>
ただし、このアプローチにはいくつかの注意点がある。<br>
* <code>dynamic</code>キーワードの使用により、型安全性が部分的に失われる。
* パフォーマンスが若干低下する可能性がある。
* オーバーフローのチェックが行われないため、大きな数値を扱う場合は注意が必要である。
<br>
<br>



2024年8月25日 (日) 14:19時点における版

概要

C#において、独自の拡張メソッドを実装する手順を記載する。

拡張メソッドを使用するには、usingディレクティブを使用して、拡張メソッドが定義されている名前空間を指定する。


拡張メソッドの実装

  1. 拡張メソッドを実装するための静的クラスを定義する。
    この静的クラスは、クライアントコードから参照できる必要がある。
  2. 拡張メソッドを静的メソッドとして実装する。
  3. メソッドの最初の引数では、操作する型を指定する。
    型名の前には、this修飾子を付加する。


名前の衝突において、呼び出し元のクラスで定義されているメソッドが優先されるため、拡張メソッドが使用されることはない。


拡張メソッドの使用

  1. 呼び出し元において、usingディレクティブを使用して、拡張メソッドの静的クラスを含む名前空間を指定する。
  2. クラスのインスタンスメソッドと同様にメソッドを記述する。
    呼び出し元では、最初の引数は指定しない。
    これは、演算子を適用する型を表すものであり、コンパイラはオブジェクトの型を既に認識しているためである。
    指定する必要があるのは、第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