C#: 高度な機能の概要
1. 振り返り
リフレクションにより、プログラムは実行時に自身の型情報を検査・操作することができます。Typeクラスはリフレクションの中核をなすもので、フィールド、プロパティ、メソッドなどのメタデータへのアクセスを提供します。
(1) 型情報の取得
CSHARP
using System;
using System.Reflection;
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public void SayHello() => Console.WriteLine("Hello!");
}
class Program
{
static void Main()
{
Type type1 = typeof(Person);
Type type2 = new Person().GetType();
Console.WriteLine($"Type name: {type1.Name}");
Console.WriteLine("Properties:");
foreach (PropertyInfo p in type1.GetProperties())
Console.WriteLine($" {p.Name} ({p.PropertyType.Name})");
Console.WriteLine("Methods:");
foreach (MethodInfo m in type1.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
Console.WriteLine($" {m.Name}");
}
}
TEXT
📖 参照専用
Type name: Person
Properties:
Name (String)
Age (Int32)
Methods:
get_Name
set_Name
get_Age
set_Age
SayHello
(2) インスタンスの動的な作成
CSHARP
using System;
using System.Reflection;
class Dog
{
public string Breed { get; set; } = "Unknown";
public override string ToString() => $"Dog: {Breed}";
}
class Program
{
static void Main()
{
Type type = typeof(Dog);
object obj = Activator.CreateInstance(type)!;
PropertyInfo prop = type.GetProperty("Breed")!;
prop.SetValue(obj, "Husky");
Console.WriteLine(obj);
}
}
TEXT
📖 参照専用
Dog: Husky
▶ サンプル
CSHARP
using System;
using System.Linq;
using System.Reflection;
class Calculator
{
public int Add(int a, int b) => a + b;
public double Multiply(double x, double y) => x * y;
public void Reset() { }
}
class Program
{
static void Main()
{
Calculator calc = new Calculator();
Type type = calc.GetType();
Console.WriteLine($"Type: {type.Name}");
Console.WriteLine($"Namespace: {type.Namespace}");
Console.WriteLine($"Is class: {type.IsClass}");
Console.WriteLine("Public methods:");
MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (MethodInfo m in methods)
{
string parameters = string.Join(", ", m.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"));
Console.WriteLine($" {m.ReturnType.Name} {m.Name}({parameters})");
}
}
}
TEXT
📖 参照専用
Type: Calculator
Namespace:
Is class: True
Public methods:
Int32 Add(Int32 a, Int32 b)
Double Multiply(Double x, Double y)
Void Reset()
2. 属性
属性とは、コード要素にメタデータを追加するために使用される宣言型タグのことです。組み込みの属性を使用することも、カスタム属性を定義することもできます。
(1) 組み込み属性
CSHARP
using System;
class LegacyService
{
[Obsolete("Please use NewService instead", error: false)]
public void OldMethod() => Console.WriteLine("Old method");
public void NewMethod() => Console.WriteLine("New method");
}
class Program
{
static void Main()
{
var service = new LegacyService();
service.OldMethod();
service.NewMethod();
}
}
TEXT
📖 参照専用
Old method
New method
💡
Obsoleteのerrorパラメータがtrueに設定されている場合、旧式のメソッドを呼び出すと、警告ではなくコンパイルエラーが発生します。
(2) カスタム属性
CSHARP
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
class DescriptionAttribute : Attribute
{
public string Text { get; }
public DescriptionAttribute(string text) => Text = text;
}
[Description("User management service")]
class UserService
{
[Description("Get all users")]
public void GetAll() { }
}
class Program
{
static void Main()
{
var attr = Attribute.GetCustomAttribute(typeof(UserService), typeof(DescriptionAttribute)) as DescriptionAttribute;
Console.WriteLine(attr?.Text ?? "No description");
}
}
TEXT
📖 参照専用
User management service
3. ジェネリック制約の詳細
ジェネリック制約では、whereキーワードを使用して型パラメータの範囲を制限し、それらが特定の条件を満たすようにします。
(1) 制約タイプの概要
| 制約 | 説明 |
|---|---|
where T : class |
T は参照型でなければならない |
where T : struct |
T は値型でなければならない(null にはできない) |
where T : new() |
T には引数なしのパブリックコンストラクタが必須である |
where T : ISpecific |
T は指定されたインターフェースを実装しなければならない |
where T : BaseClass |
T は指定された基底クラスを継承していなければならない |
where T : unmanaged |
T は非管理型でなければならない |
where T : notnull |
T は null を許さない型でなければならない |
▶ サンプル
CSHARP
using System;
interface IRepository
{
string GetName();
}
class SqlRepository : IRepository
{
public string GetName() => "SQL Repository";
}
class Service```<T>``` where T : class, IRepository, new()
{
private T _repo = new T();
public void Print() => Console.WriteLine(_repo.GetName());
}
class Program
{
static void Main()
{
var service = new Service<SqlRepository>();
service.Print();
}
}
TEXT
📖 参照専用
SQL Repository
⚠️
new()という制約は、制約リストの最後に配置する必要があります。最初にclassまたはstructを配置してください。
4. イテレータ(yield)
イテレータはyield returnを使用してシーケンスの要素を1つずつ生成し、遅延評価を実現しています。つまり、要素は反復処理が行われるときにのみ計算されます。
(1) 利回り(yield return)と利回りブレイク(yield break)
CSHARP
using System;
using System.Collections.Generic;
class Program
{
static IEnumerable```<int>``` GetNumbers()
{
for (int i = 0; i < 5; i++)
{
if (i == 3) yield break;
yield return i;
}
}
static void Main()
{
foreach (int n in GetNumbers())
Console.WriteLine(n);
}
}
TEXT
📖 参照専用
0
1
2
💡
yield break は反復処理を直ちに終了させます。イテレータのメソッドは IEnumerable<T> または IEnumerator<T> を返します。
▶ サンプル
CSHARP
using System;
using System.Collections.Generic;
class Program
{
static IEnumerable```<int>``` Fibonacci(int count)
{
int a = 0;
int b = 1;
for (int i = 0; i < count; i++)
{
yield return a;
int temp = a + b;
a = b;
b = temp;
}
}
static void Main()
{
Console.WriteLine("Fibonacci sequence, first 10 terms:");
foreach (int n in Fibonacci(10))
{
Console.Write(n + " ");
}
Console.WriteLine();
}
}
TEXT
📖 参照専用
Fibonacci sequence, first 10 terms:
0 1 1 2 3 5 8 13 21 34
5. スパン<T>とメモリ<T>
Span<T>およびMemory<T>は、連続したメモリ領域へのコピー不要のスライスアクセスを提供するため、高性能が求められるシナリオにおいて重要な型となっています。
(1) Span<T> 基本的な使い方
CSHARP
using System;
class Program
{
static void Main()
{
int[] array = { 10, 20, 30, 40, 50 };
Span```<int>``` span = array.AsSpan();
Span```<int>``` slice = span.Slice(1, 3);
slice[0] = 99;
Console.WriteLine(string.Join(", ", array));
Span```<int>``` stackSpan = stackalloc int[3];
stackSpan[0] = 1;
stackSpan[1] = 2;
stackSpan[2] = 3;
Console.WriteLine(string.Join(", ", stackSpan.ToArray()));
}
}
TEXT
📖 参照専用
10, 99, 30, 40, 50
1, 2, 3
⚠️
Span<T>はスタック上にのみ格納可能であり、クラスフィールドとして使用したり、awaitの境界を越えて使用したりすることはできません。スタックフレームをまたぐ必要がある場合は、Memory<T>を使用してください。
6. 正規表現(Regex)
System.Text.RegularExpressions.Regexクラスは、テキストのパターンマッチング、検索、および置換に使用されます。
(1) 一般的な操作
CSHARP
using System;
using System.Text.RegularExpressions;
class Program
{
static void Main()
{
string input = "Contact phone: 138-1234-5678, Email: test@example.com";
bool hasPhone = Regex.IsMatch(input, @"\d{3}-\d{4}-\d{4}");
Console.WriteLine($"Contains phone: {hasPhone}");
Match phoneMatch = Regex.Match(input, @"\d{3}-\d{4}-\d{4}");
Console.WriteLine($"Phone: {phoneMatch.Value}");
MatchCollection emails = Regex.Matches(input, @"[\w.]+@[\w.]+");
foreach (Match m in emails)
Console.WriteLine($"Email: {m.Value}");
string replaced = Regex.Replace(input, @"\d{4}", "****");
Console.WriteLine($"Replaced: {replaced}");
}
}
TEXT
📖 参照専用
Contains phone: True
Phone: 138-1234-5678
Email: test@example.com
Replaced: Contact phone: 138-****-****, Email: test@example.com
(2) 一般的なパターンのクイックリファレンス
| パターン | 説明 |
|---|---|
\d |
数字 |
\w |
英字・数字・アンダースコア |
\s |
空白 |
. |
任意の文字(改行を除く) |
^ / $ |
行の先頭 / 行の末尾 |
{n,m} |
nをm回繰り返す |
[abc] |
文字セット |
7. 安全でないコード
unsafeコンテキストでは、ポインタや手動によるメモリ操作の使用が可能であり、相互運用や極限のパフォーマンスが求められるシナリオに適しています。
(1) ポインタと固定
CSHARP
using System;
class Program
{
static unsafe void Main()
{
int value = 42;
int* ptr = &value;
Console.WriteLine($"Value: {*ptr}");
Console.WriteLine($"Address: {(long)ptr}");
int[] arr = { 1, 2, 3 };
fixed (int* p = arr)
{
Console.WriteLine($"First element: {*p}");
Console.WriteLine($"Second element: {*(p + 1)}");
}
}
}
TEXT
📖 参照専用
Value: 42
Address: 12345678
First element: 1
Second element: 2
⚠️
unsafeを使用するには、<AllowUnsafeBlocks>true</AllowUnsafeBlocks>`` in the project file. The fixed` ステートメントでオブジェクトを固定し、GCによるメモリの移動を防ぐ必要があります。
8. C# バージョンの機能概要 (7–12)
| バージョン | 主な機能 | 概要 |
|---|---|---|
| C# 7 | out 変数、タプル、パターンマッチング | out int x インライン宣言、(int, string) タプル、is Type パターン |
| C# 7 | ローカル関数、参照型戻り値 | メソッド内のネストされた関数、参照型戻り値 |
| C# 8 | ヌル許容参照型、非同期ストリーム | string? ヌル許容アノテーション、IAsyncEnumerable<T> |
| C# 8 | インターフェースのデフォルトメソッド、インデックス、および範囲 | インターフェースのデフォルト実装、[^1]、[1..3] |
| C# 9 | レコード型、初期化セッター | record 不変型、init 初期化専用プロパティ |
| C# 9 | トップレベル文、ターゲット型指定の new | Main の定型コード不要、new() 型の省略 |
| C# 10 | グローバルな using 宣言、ファイルスコープのネームスペース | global using X;, namespace Ns; 1行 |
| C# 10 | レコード構造体、定数補間文字列 | record struct、定数補間文字列 |
| C# 11 | 生の文字列リテラル、必須 | """...""" 複数行の生の文字列、required 強制初期化 |
| C# 11 | リストパターン、UTF-8 リテラル | [1, 2, ..] マッチング、"hello"u8 |
| C# 12 | プライマリコンストラクタ | class C(int x) コンストラクタとしてのクラス宣言 |
| C# 12 | コレクション式、任意の型のエイリアス | [1, 2, 3] 一括初期化、using P = int* |
💡 このレッスンでは、高度な機能の概要を説明します。各トピックについては、今後の専用レッスンで詳しく取り上げます。
❓ よくある質問
Q リフレクションにはパフォーマンス上のオーバーヘッドがありますか?
A はい、リフレクションは直接呼び出しに比べて数十倍遅くなります。頻繁に使用する場合は、MethodInfo をキャッシュするか、式ツリーに切り替えてください。
Q Span
<T>とMemory<T>の違いは何ですか?A Spanはスタック上にのみ存在でき、非同期/ストレージの境界を越えることはできません。一方、Memoryはヒープ上に存在でき、非同期の境界を越えることができ、.Spanを介してアクセスされます。
Q yield のリターンはマルチスレッド対応ですか?
A いいえ、yield はシングルスレッドの遅延列挙メカニズムです。要素は MoveNext() が呼び出されたときにのみ計算されます。
Q どのような場合に「安全でないコード」が必要になるのでしょうか?
A C/C++との相互運用、アンマネージドメモリ操作、あるいは極限のパフォーマンスが必要な場合に使用してください。日常的な開発では使用を避けてください。
📖 まとめ
- リフレクションは、実行時に Type クラスを介して型のメタデータにアクセスし、動的にインスタンスを作成することができます
- 属性はコードに宣言型メタデータを追加し、カスタム定義や実行時の読み取りをサポートします
- ジェネリック制約(where)は、型パラメータを参照型、値型、コンストラクタを持つ型などに制限します。
yield returnは遅延反復を実行し、yield breakは列挙を終了させる- Span
<T>はスタックベースのゼロコピースライシングを提供します。Memory<T>は非同期境界をまたぐことができます。 - Regex クラスは、パターンマッチング、検索、置換、およびその他のテキスト操作をサポートしています
- この安全でないコンテキストではポインタ操作が許可されているため、メモリを固定するために
fixedキーワードが必要となる - C# 7~12では、タプル、Null許容参照、レコード型、プライマリコンストラクタなどが順次導入されました
📝 練習問題
- リフレクションを使用して、
stringクラスのすべてのパブリックメソッド名を取得し、それらを出力する - カスタム属性
[Author]を作成し、クラスに適用して、リフレクションを使って読み取る - インスタンスを作成して返す汎用メソッド
T Create``<T>``() where T : new()を作成する yield returnを使用して、フィボナッチ数列の最初の 20 項を生成するイテレータを実装してください。- 正規表現(Regex)を使用して、文字列が有効なIPv4アドレスの形式であるかどうかを検証する