LoginSignup
5
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-10-29

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

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

目的

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

クラス情報

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";
    }
}

とりあえずアップキャストしてみる

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

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

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

色々思考錯誤した結果・・・

型に基底クラスを指定すれば良くね?という安易な手法で出力することに。

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

とりあえず出せたけど・・・

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

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

追記(2018/10/31)

ありがたいことにコメントでアドバイスを貰えました!それを元に再検討してみました。

DeclaringTypeを使う

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

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));
            }
        }
    }
}
出力結果
DeclaringType:Derived
z:100
x:Derived
DeclaringType:Base
a:10
b:Base

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

BaseTypeを使う

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

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));
        }

    }
}
出力結果
BaseType:Base
a:10
b:Base

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

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));
        }

    }
出力結果
BaseType:System.Object

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

BaseTypeと属性を利用する

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

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";
    }
}
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));
        }
    }
}
出力結果
a:10
b:Base

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

5
2
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2