【C#】オブジェクトのプロパティをリフレクションでTrimする

エンジニアの今井です。
C#でコードを書いているとたまにリフレクションを使ってオブジェクトを操作したいことがありますよね?
そこで、今回はオブジェクトにあるstring型のプロパティをTrimする事例を紹介します。

ゴール

オブジェクトのプロパティをリフレクションを使ってTrimし、再代入する。

今回はHogeオブジェクトにTrimObjメソッドを通すことで、Hogeオブジェクト内のプロパティをTrimする。

public class Hoge
{
	public string Foo { get; set; }
	public string Bar { get; set; }
}
var hoge = new Hoge()
{
	Foo = " foo ",
	Bar = "bar   "
};

// ここでTrimを使う
TrimObj(hoge);

Console.WriteLine(hoge.Foo); // fooと出力されたら成功
Console.WriteLine(hoge.Bar); // barと出力されたら成功

実装するコード

まずはメソッドの全体像を示します。

void TrimObj(object obj)
{
	// (A) プロパティを取得する
	var properties = obj
		.GetType()
		.GetProperties();

	// (B) プロパティをもとにTrimを実行し、再代入する
	foreach (var property in properties)
	{
		// プロパティの値を取得する
		var value = property.GetValue(obj);

		// string型だったらstring型sに変換する。それ以外の場合はcontinueを実行する
		if (value is not string s) continue;

		// Trimを実行する
		var trimmedValue = s.Trim();

		// プロパティに値を再代入する
		property.SetValue(obj, trimmedValue);
	}
}

上のコードは大きく分けて二つの箇所にわかれます。一つ目はプロパティを取得する処理A。二つ目は取得したプロパティをもとにTrimを実行し、再代入する処理Bです。

処理Aは次のコードになります。見ての通り、object型の全プロパティを取得する処理です。

// (A) プロパティを取得する
var properties = obj
	.GetType()
	.GetProperties();

処理Bは次のコードになります。処理の流れ次の通りです
1. GetValueで値を取得する
2. string型だったら変数sに変換する。違ったら処理を抜ける
3. trimを実行する
4. trim後の値をSetValueで設定する

// (B) プロパティをもとにTrimを実行し、再代入する
foreach (var property in properties)
{
	// プロパティの値を取得する
	var value = property.GetValue(obj);

	// string型だったらstring型sに変換する。それ以外の場合はcontinueを実行する
	if (value is not string s) continue;

	// Trimを実行する
	var trimmedValue = s.Trim();

	// プロパティに値を再代入する
	property.SetValue(obj, trimmedValue);
}

このTrimObjメソッドを活用することでオブジェクトの値を自動でTrimできます。このコードの全体像は次になります。

var hoge = new Hoge()
{
	Foo = " foo ",
	Bar = "bar   "
};

// ここでTrimを使う
TrimObj(hoge);

Console.WriteLine(hoge.Foo); // fooと出力されたら成功
Console.WriteLine(hoge.Bar); // barと出力されたら成功

void TrimObj(object obj)
{
	// プロパティを取得する
	var properties = obj
	.GetType()
	.GetProperties();

	foreach (var property in properties)
	{
		// プロパティの値を取得する
		var value = property.GetValue(obj);

		// string型だったらstring型sに変換する。それ以外の場合はcontinueを実行する
		if (value is not string s) continue;

		// Trimを実行する
		var trimmedValue = s.Trim();

		// プロパティに値を再代入する
		property.SetValue(obj, trimmedValue);
	}
}

public class Hoge 
{
	public string Foo { get; set; }
	public string Bar { get; set; }
}

一部のプロパティは無視したい

さて、実際に上にあげたような処理でstringをTrimできたとします。しかし、往々にして実務には例外というものがあります。上の仕組みでTrimしたいが一部のプロパティだけこのTrimの対象外にしたい場合を考えてみましょう。

次のゴール

Hogeオブジェクトを用意し、TrimObjメソッドでTrimを行う。このとき、FooとBarはTrimを実行するが、Piyoには実行しないようにする。

public class Hoge
{
	public string Foo { get; set; }
	public string Bar { get; set; }
	public string Piyo { get; set; }
}
var hoge = new Hoge()
{
	Foo = " foo ",
	Bar = "bar   ",
	Piyo = "   piyo"
};

// ここでTrimを使う
TrimObj(hoge);

Console.WriteLine(hoge.Foo); // "foo"と出力されたら成功
Console.WriteLine(hoge.Bar); // "bar"と出力されたら成功
Console.WriteLine(hoge.Piyo); // "   piyo"と出力されたら成功

一部だけTrimを回避するコード

今回は、NoTrimAttributeというAttributeを用意し、このAttributeが指定されているプロパティはTrimの対象外にする仕様とします。

[AttributeUsage(AttributeTargets.Property)]
public class NoTrimAttribute : Attribute
{
}

TrimしたくないのはPiyoのため、NoTrimAttributeは次のように指定します。

public class Hoge
{
	public string Foo { get; set; }
	public string Bar { get; set; }
	[NoTrim]
	public string Piyo { get; set; }
}

あとはNoTrimAttributeが指定されているプロパティだけ処理を避ければいいだけです。これができるメソッドは次のものです。

void TrimObj(object obj)
{
	// プロパティを取得する
	var properties = obj
		.GetType()
		.GetProperties()
		.Where(p => p.GetCustomAttributes(typeof(NoTrimAttribute), true).Any() == false);

	foreach (var property in properties)
	{
		// プロパティの値を取得する
		var value = property.GetValue(obj);

		// string型だったらstring型sに変換する。それ以外の場合はcontinueを実行する
		if (value is not string s) continue;

		// Trimを実行する
		var trimmedValue = s.Trim();

		// プロパティに値を再代入する
		property.SetValue(obj, trimmedValue);
	}
}

以前のものとの違いは、次の箇所のWhereメソッドだけです。このメソッドにより、NoTrimAttributeが指定されているプロパティは対象から外すことができます。

// プロパティを取得する
var properties = obj
	.GetType()
	.GetProperties()
	.Where(p => p.GetCustomAttributes(typeof(NoTrimAttribute), true).Any() == false);

以上から全コードを書くと次のようになります。

var hoge = new Hoge()
{
	Foo = " foo ",
	Bar = "bar   ",
	Piyo = "   piyo"
};

// ここでTrimを使う
TrimObj(hoge);

Console.WriteLine(hoge.Foo); // "foo"と出力されたら成功
Console.WriteLine(hoge.Bar); // "bar"と出力されたら成功
Console.WriteLine(hoge.Piyo); // "   piyo"と出力されたら成功

void TrimObj(object obj)
{
	// プロパティを取得する
	var properties = obj
		.GetType()
		.GetProperties()
		.Where(p => p.GetCustomAttributes(typeof(NoTrimAttribute), true).Any() == false);

	foreach (var property in properties)
	{
		// プロパティの値を取得する
		var value = property.GetValue(obj);

		// string型だったらstring型sに変換する。それ以外の場合はcontinueを実行する
		if (value is not string s) continue;

		// Trimを実行する
		var trimmedValue = s.Trim();

		// プロパティに値を再代入する
		property.SetValue(obj, trimmedValue);
	}
}

[AttributeUsage(AttributeTargets.Property)]
public class NoTrimAttribute : Attribute
{
}

public class Hoge
{
	public string Foo { get; set; }
	public string Bar { get; set; }
	[NoTrim]
	public string Piyo { get; set; }
}

まとめ

今回はリフレクションを使った一括処理についてご紹介しました。リフレクションはわかりにくいですが、使用箇所によっては便利です。今回のようなことを学べばより柔軟に処理を書くことができるでしょう。
読んでくださった方の参考になれば幸いです。

関連記事

プロジェクトストーリー

技術

コメント

この記事へのコメントはありません。

カテゴリー

TOP
TOP