適切な拡張の設計
ベストプラクティスを適用し、落とし穴を避けます。
「適切な拡張の設計」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。
使いやすい拡張メソッドを設計する
拡張メソッドは強力ですが、その強力さは誤用にもつながります。優れた拡張メソッドは型本来の機能のように自然に感じられ、見つけやすく、呼び出し側を決して驚かせません。このレッスンでは、実践的なガイドラインを学びます。
型の本質ではなく動作を拡張する
パイプライン内で読みやすくなるユーティリティ動作には、拡張メソッドを使うのが適しています。本当に自分が所有する型に属するメソッドであれば、通常のメンバーとして実装してください。
using System;
public static class TimeExtensions
{
// A pleasant, focused helper
public static bool IsWeekend(this DayOfWeek day)
=> day == DayOfWeek.Saturday || day == DayOfWeek.Sunday;
}
public class Program
{
public static void Main()
{
Console.WriteLine(DayOfWeek.Sunday.IsWeekend());
Console.WriteLine(DayOfWeek.Monday.IsWeekend());
}
}可能な限りメソッドを純粋に保つ
新しい値を返し、副作用を避ける拡張メソッドは、動作を理解しやすく、つなぎ合わせるのも簡単です。入力を受け取り、値を返す形を目指してください。
using System;
public static class StringExtensions
{
// Pure: depends only on input, returns a new string
public static string Truncate(this string s, int max)
=> s.Length <= max ? s : s.Substring(0, max) + "...";
}
public class Program
{
public static void Main()
{
Console.WriteLine("Extension methods are great".Truncate(10));
}
}null 入力に対処する
呼び出し側は null のレシーバーに対しても拡張メソッドを呼び出せるため、どう動作させるかを意識して決めてください。null を適切に受け入れるか、明確な ArgumentNullException をスローします。
using System;
public static class StringExtensions
{
public static string SafeUpper(this string s)
{
if (s is null) throw new ArgumentNullException(nameof(s));
return s.ToUpper();
}
}
public class Program
{
public static void Main()
{
try { ((string)null).SafeUpper(); }
catch (ArgumentNullException ex) { Console.WriteLine("Caught: " + ex.ParamName); }
Console.WriteLine("ok".SafeUpper());
}
}見つけやすい名前を付ける
開発者はドットを入力して IntelliSense の候補を確認しながら拡張メソッドを見つけます。結果を説明する、動詞を使った明確な名前にすると、ヘルパーが見つけやすくなり、名前だけで動作も理解できます。
using System;
using System.Collections.Generic;
public static class CollectionExtensions
{
public static bool HasItems<T>(this ICollection<T> c)
=> c != null && c.Count > 0;
}
public class Program
{
public static void Main()
{
var list = new List<int> { 1 };
Console.WriteLine(list.HasItems());
}
}関連する拡張メソッドをまとめて整理する
1 つの概念に関する拡張メソッドは、適切な名前を付けた 1 つの静的クラスと名前空間にまとめてください。これによりインポートの意図が明確になり、利用者は必要なヘルパーだけを取り込めます。
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static IEnumerable<T> TakeFirst<T>(this IEnumerable<T> source, int n)
{
int taken = 0;
foreach (var item in source)
{
if (taken++ >= n) yield break;
yield return item;
}
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 1, 2, 3, 4 };
Console.WriteLine(string.Join(",", nums.TakeFirst(2)));
}
}意外な動作を隠さない
軽い処理に見えるのに高コストな処理や状態を持つ処理を行う拡張メソッドは、期待に反します。コストを明確にし、データベースへのクエリや状態の変更を行う場合は、それに応じた名前を付けてください。
using System;
public static class MathExtensions
{
// Cheap and obvious: no hidden cost
public static int ClampMin(this int value, int min)
=> value < min ? min : value;
}
public class Program
{
public static void Main()
{
Console.WriteLine((-3).ClampMin(0));
Console.WriteLine(5.ClampMin(0));
}
}object の拡張を避ける
object を拡張すると、プログラム内のすべての型にメソッドが追加され、あらゆる場所の IntelliSense が煩雑になります。代わりに、目的に合う最も具体的な型またはインターフェイスを対象にしてください。
using System;
public static class IntExtensions
{
// Specific to int, not object - far less noisy
public static string ToOrdinal(this int n)
{
if (n % 10 == 1 && n % 100 != 11) return n + "st";
if (n % 10 == 2 && n % 100 != 12) return n + "nd";
if (n % 10 == 3 && n % 100 != 13) return n + "rd";
return n + "th";
}
}
public class Program
{
public static void Main()
{
Console.WriteLine(1.ToOrdinal());
Console.WriteLine(22.ToOrdinal());
}
}シーケンスには IEnumerable の返却を優先する
シーケンス演算子では、IEnumerable<T> を返し、遅延実行を使用してください。これにより合成しやすくなり、呼び出し側が必要としないデータまで強制的に具体化せずに済みます。
using System;
using System.Collections.Generic;
public static class SeqExtensions
{
public static IEnumerable<int> Doubled(this IEnumerable<int> source)
{
foreach (var n in source) yield return n * 2;
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 1, 2, 3 };
foreach (var n in nums.Doubled()) Console.WriteLine(n);
}
}優れた拡張メソッドのチェックリスト
拡張メソッドを公開する前に、次の点を確認してください。名前は明確ですか。純粋な処理ですか、それとも副作用が明らかになっていますか。null を処理していますか。対象の型は具体的ですか。適切にまとめられていますか。すべてに「はい」と答えられるなら、他の開発者も快適に使えるヘルパーになっています。
using System;
public static class StringExtensions
{
public static string OrDefault(this string s, string fallback)
=> string.IsNullOrEmpty(s) ? fallback : s;
}
public class Program
{
public static void Main()
{
Console.WriteLine("".OrDefault("N/A"));
Console.WriteLine("value".OrDefault("N/A"));
}
}自分で試す
チェックリストを適用してみましょう。具体的な対象型、明確な名前、null への対応、純粋な結果を備えています。この小さな拡張メソッドは、すべてのガイドラインを満たしています。
using System;
using System.Collections.Generic;
public static class DictionaryExtensions
{
// Clear name, specific type, null-safe, pure
public static TValue GetOrDefault<TKey, TValue>(
this IReadOnlyDictionary<TKey, TValue> dict, TKey key, TValue fallback)
{
if (dict == null) return fallback;
return dict.TryGetValue(key, out var value) ? value : fallback;
}
}
public class Program
{
public static void Main()
{
var ages = new Dictionary<string, int> { ["Ann"] = 30 };
Console.WriteLine(ages.GetOrDefault("Ann", -1));
Console.WriteLine(ages.GetOrDefault("Bob", -1));
}
}クイックチェック
設計のガイドラインを適用してみましょう。
まとめ
適切に設計された拡張メソッドは、型本来の機能のように自然で予測しやすいものです。
- 純粋で値を返し、つなぎ合わせやすいヘルパーを優先します。
- null に対する動作を決め、文書化します。
- 見つけやすいように、動詞を使った明確な名前を付けます。
objectではなく、具体的な型またはインターフェイスを対象にします。- 関連するヘルパーをまとめ、シーケンスには
IEnumerable<T>を返します。
よくある質問
「適切な拡張の設計」レッスンは無料ですか?
はい。「適切な拡張の設計」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。
「適切な拡張の設計」で何を学びますか?
ベストプラクティスを適用し、落とし穴を避けます。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C# Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「適切な拡張の設計」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC# Academyレッスンでコードを書いて実行できますか?
はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。