【C#】派生クラスから基底クラスのみのフィールド情報を取得したい

Qiita初投稿。C#歴半年程度の知識でやってみたら思ったより手間がかかってしまったのでメモ。

**2018/10/31 コメントを元に追記しました。**

# 目的
派生クラスから基底クラスのみのフィールド情報とその値を取得したい。

# クラス情報

```cs:SampleClass.cs
public class Base {
	public int a;
	public string b;

	public Base() {
		a = 10;
		b = "Base";
	}
}

public class Derived : Base {
	public int z;
	public string x;

	public Derived() {
		z = 100;
		x = "Derived";
	}
}
```

# とりあえずアップキャストしてみる
アップキャストした後にリフレクションを使って出力を試みる。

```cs:Program.cs
public class Program {
	static void Main(string[] args) {
		Derived derived = new Derived();
		Base based = derived;

		var field = based.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

		foreach (var item in field) {
			Console.WriteLine(item.Name + ":" + item.GetValue(based));
		}
	}
}

```

これを実行してみると・・・

```
z:100
x:Derived
a:10
b:Base
```

派生クラスの情報も出力されてしまう・・・。

# 色々思考錯誤した結果・・・
型に基底クラスを指定すれば良くね？という安易な手法で出力することに。

```cs:Program.cs
public class Program {
	static void Main(string[] args) {
		Derived derived = new Derived();

		var type = typeof(Base);
		var field = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

		foreach (var item in field) {
			Console.WriteLine(item.Name + ":" + item.GetValue(derived));
		}
	}
}
```

出力結果

```
a:10
b:Base
```

# とりあえず出せたけど・・・
元々は複数のクラスのフィールド情報をテキストファイルに吐き出すという処理を行うために実装しました。<br>しかし、特定のクラスは基底クラスだけの情報のみを吐き出したいため、今回のような対応を入れています。

一応目的は達成できましたが、特定のクラスのみわざわざ型を指定するのも面倒くさいので、もっとスマートにできる方法があれば教えて欲しいところです。

# 追記(2018/10/31)
ありがたいことにコメントでアドバイスを貰えました！それを元に再検討してみました。

## DeclaringTypeを使う
`DeclaringType`は対象メンバを宣言するクラス情報を持つプロパティです。<br>次のコードでは派生クラスのフィールド情報を取得して、`DeclaringType`をグループ分けをすることで`DeclaringType`をキーとするIGrouping<T>を取得することができます。

```cs:Program.cs
public class Program {
	static void Main(string[] args) {
		var derived = new Derived();

		var group = derived
			.GetType()
			.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
			.GroupBy(x => x.DeclaringType);

		foreach (var item in group) {
			Console.WriteLine("DeclaringType:" + item.Key);
			foreach (var field in item) {
				Console.WriteLine(field.Name + ":" + field.GetValue(derived));
			}
		}
	}
}
```

```text:出力結果
DeclaringType:Derived
z:100
x:Derived
DeclaringType:Base
a:10
b:Base
```

欲しいクラスの情報のみを取得したい場合は別途`DeclaringType`の値を見る必要があるため、未継承/継承クラスが入り交じると使いづらいかもしれません。<br>どちらかというと基底/派生問わず特定クラスの情報のみが欲しい場合は有用に感じました。

## BaseTypeを使う
`BaseType`は対象クラスの継承元の情報を持つプロパティです。<br>次のコードでは`BaseType`を使って基底クラスのフィールド情報を取得しています。

```cs:Program.cs
public class Program {
	static void Main(string[] args) {
		var derived = new Derived();

		var field = derived
			.GetType()
			.BaseType
			.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

		Console.WriteLine("BaseType:" + derived.GetType().BaseType);
		foreach (var item in field) {
			Console.WriteLine(item.Name + ":" + item.GetValue(derived));
		}

	}
}
```

```text:出力結果
BaseType:Base
a:10
b:Base
```

ただし、継承していない場合は`System.Object`を取得します。

```cs:Program.cs
	static void Main(string[] args) {
		var based = new Base();

		var field = based
			.GetType()
			.BaseType
			.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

		Console.WriteLine("BaseType:" + based.GetType().BaseType);
		foreach (var item in field) {
			Console.WriteLine(item.Name + ":" + item.GetValue(based));
		}

	}
```

```text:出力結果
BaseType:System.Object
```

`System.Object`の場合は`BaseType`を見ないという条件をつければ未継承クラスが同じ処理を通ってもうまくいきそうです。

## BaseTypeと属性を利用する
派生クラスが複数ある場合、片方のクラスは基底クラスのみを出したいけれどもう片方は派生クラス込みで出したい等が出るかもしれません。<br>それに対して一々条件を追加するのはよろしくないので属性を併用してみます。

```cs:SampleClass.cs
[System.AttributeUsage(System.AttributeTargets.Class)]
public class IgnoreDerivedField : System.Attribute { }

public class Base {
	public int a;
	public string b;

	public Base() {
		a = 10;
		b = "Base";
	}
}

[IgnoreDerivedField]
public class Derived : Base {
	public int z;
	public string x;

	public Derived() {
		z = 100;
		x = "Derived";
	}
}
```

```cs:Program.cs
public class Program {
	static void Main(string[] args) {
		var derived = new Derived();
		var type = derived.GetType();

		if (type.GetCustomAttribute<IgnoreDerivedField>() != null) {
			type = type.BaseType;
		}

		var field = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

		foreach (var item in field) {
			Console.WriteLine(item.Name + ":" + item.GetValue(derived));
		}
	}
}
```

```text:出力結果
a:10
b:Base
```

コードもスッキリするし、この方法が一番良さそうな感じです。