反射

リフレクションは、実行時に動的オブジェクト プロパティにアクセスするための C# 言語メカニズムです。通常、リフレクションは、動的オブジェクト タイプとオブジェクト属性値に関する情報を取得するために使用されます。たとえば、REST アプリケーションでは、リフレクションを使用して、シリアル化された応答オブジェクトを反復処理できます。

備考:MS ガイドラインによると、パフォーマンス クリティカルなコードはリフレクションを避ける必要があります。 https://msdn.microsoft.com/en-us/library/ff647790.aspx を参照してください

# 型のメンバーを取得

using System;
using System.Reflection;
using System.Linq;
                
public class Program
{
  public static void Main()
  {
    var members = typeof(object)
                    .GetMembers(BindingFlags.Public |
                                BindingFlags.Static |
                                BindingFlags.Instance);
    
    foreach (var member in members)
    {
      bool inherited = member.DeclaringType.Equals( typeof(object).Name );
      Console.WriteLine($"{member.Name} is a {member.MemberType}, " +
                        $"it has {(inherited ? "":"not")} been inherited.");
    }
  }
}

出力 (さらに下の出力順序に関する注記を参照 ):

GetMembers() も使用できます BindingFlags を渡さずに . すべてを返します その特定のタイプのパブリック メンバー。

GetMembers ということに注意してください。 特定の順序でメンバーを返すわけではないため、GetMembers の順序に依存しないでください。

デモを見る

# メソッドを取得して呼び出す

インスタンス メソッドを取得して呼び出す

using System;
                
public class Program
{
    public static void Main()
    {
        var theString = "hello";
        var method = theString
                     .GetType()
                     .GetMethod("Substring",
                                new[] {typeof(int), typeof(int)}); //The types of the method arguments
         var result = method.Invoke(theString, new object[] {0, 4});
         Console.WriteLine(result);
    }
}

出力:

地獄

デモを見る

静的メソッドを取得して呼び出す

一方、メソッドが静的である場合、それを呼び出すためのインスタンスは必要ありません。

var method = typeof(Math).GetMethod("Exp");
var result = method.Invoke(null, new object[] {2});//Pass null as the first argument (no need for an instance)
Console.WriteLine(result); //You'll get e^2

出力:

7.38905609893065

デモを見る

# タイプのインスタンスの作成

最も簡単な方法は、Activator を使用することです。 クラス。

ただし、 Activator でも Activator.CreateInstance() を使用して、.NET 3.5 以降のパフォーマンスが向上しました。 (比較的) パフォーマンスが低いため、場合によっては不適切なオプションです:テスト 1 、テスト 2 、テスト 3 ...

# Activator で クラス

Type type = typeof(BigInteger);
object result = Activator.CreateInstance(type); //Requires parameterless constructor.
Console.WriteLine(result); //Output: 0
result = Activator.CreateInstance(type, 123); //Requires a constructor which can receive an 'int' compatible argument.
Console.WriteLine(result); //Output: 123

オブジェクト配列を Activator.CreateInstance に渡すことができます 複数のパラメータがある場合。

// With a constructor such as MyClass(int, int, string)
Activator.CreateInstance(typeof(MyClass), new object[] { 1, 2, "Hello World" });

Type type = typeof(someObject);
var instance = Activator.CreateInstance(type);

ジェネリック型の場合

MakeGenericType メソッドはオープン ジェネリック型 (List<> など) に変換します。 ) を具体的な型 (List<string> など) に ) に型引数を適用することによって。

// generic List with no parameters
Type openType = typeof(List<>);

// To create a List<string>
Type[] tArgs = { typeof(string) };
Type target = openType.MakeGenericType(tArgs);

// Create an instance - Activator.CreateInstance will call the default constructor.
// This is equivalent to calling new List<string>().
List<string> result = (List<string>)Activator.CreateInstance(target);

List<> 構文は typeof の外では許可されていません

# Activator なし クラス

new の使用 キーワード (パラメーターなしのコンストラクターの場合)

T GetInstance<T>() where T : new()
{
    T instance = new T();
    return instance;
}

Invoke メソッドの使用

// Get the instance of the desired constructor (here it takes a string as a parameter).
ConstructorInfo c = typeof(T).GetConstructor(new[] { typeof(string) }); 
// Don't forget to check if such constructor exists
if (c == null) 
    throw new InvalidOperationException(string.Format("A constructor for type '{0}' was not found.", typeof(T)));
T instance = (T)c.Invoke(new object[] { "test" });

式ツリーの使用

式ツリーは、各ノードが式であるツリー状のデータ構造でコードを表します。MSDN の説明:

式は、単一の値、オブジェクト、メソッド、または名前空間に評価できる 1 つ以上のオペランドと 0 個以上の演算子のシーケンスです。式は、リテラル値、メソッド呼び出し、演算子とそのオペランド、または単純な名前で構成できます。 Simplenames は、変数、型メンバー、メソッド パラメーター、名前空間、または型の名前にすることができます。

public class GenericFactory<TKey, TType>
    {
       private readonly Dictionary<TKey, Func<object[], TType>> _registeredTypes; // dictionary, that holds constructor functions.
       private object _locker = new object(); // object for locking dictionary, to guarantee thread safety

        public GenericFactory()
        {
            _registeredTypes = new Dictionary<TKey, Func<object[], TType>>();
        }

        /// <summary>
        /// Find and register suitable constructor for type
        /// </summary>
        /// <typeparam name="TType"></typeparam>
        /// <param name="key">Key for this constructor</param>
        /// <param name="parameters">Parameters</param>
        public void Register(TKey key, params Type[] parameters)
        {
            ConstructorInfo ci = typeof(TType).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.HasThis, parameters, new ParameterModifier[] { }); // Get the instance of ctor.
            if (ci == null)
                throw new InvalidOperationException(string.Format("Constructor for type '{0}' was not found.", typeof(TType)));

            Func<object[], TType> ctor;

            lock (_locker)
            {
                if (!_registeredTypes.TryGetValue(key, out ctor)) // check if such ctor already been registered
                {
                    var pExp = Expression.Parameter(typeof(object[]), "arguments"); // create parameter Expression
                    var ctorParams = ci.GetParameters(); // get parameter info from constructor

                    var argExpressions = new Expression[ctorParams.Length]; // array that will contains parameter expessions
                    for (var i = 0; i < parameters.Length; i++)
                    {

                        var indexedAcccess = Expression.ArrayIndex(pExp, Expression.Constant(i));

                        if (!parameters[i].IsClass && !parameters[i].IsInterface) // check if parameter is a value type
                        {
                            var localVariable = Expression.Variable(parameters[i], "localVariable"); // if so - we should create local variable that will store paraameter value

                            var block = Expression.Block(new[] { localVariable },
                                    Expression.IfThenElse(Expression.Equal(indexedAcccess, Expression.Constant(null)),
                                        Expression.Assign(localVariable, Expression.Default(parameters[i])),
                                        Expression.Assign(localVariable, Expression.Convert(indexedAcccess, parameters[i]))
                                    ),
                                    localVariable
                                );

                            argExpressions[i] = block;

                        }
                        else
                            argExpressions[i] = Expression.Convert(indexedAcccess, parameters[i]);
                    }
                    var newExpr = Expression.New(ci, argExpressions); // create expression that represents call to specified ctor with the specified arguments.
  
                    _registeredTypes.Add(key, Expression.Lambda(newExpr, new[] { pExp }).Compile() as Func<object[], TType>); // compile expression to create delegate, and add fucntion to dictionary
                }
            }
        }

        /// <summary>
        /// Returns instance of registered type by key.
        /// </summary>
        /// <typeparam name="TType"></typeparam>
        /// <param name="key"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public TType Create(TKey key, params object[] args)
        {
            Func<object[], TType> foo;
            if (_registeredTypes.TryGetValue(key, out foo))
            {
                return (TType)foo(args);
            }

            throw new ArgumentException("No type registered for this key.");
        }
    }

次のように使用できます:


public class TestClass
 {
        public TestClass(string parameter)
        {
            Console.Write(parameter);
        }
 } 


public void TestMethod()
{
       var factory = new GenericFactory<string, TestClass>();
       factory.Register("key", typeof(string));
       TestClass newInstance = factory.Create("key", "testParameter");
}

FormatterServices.GetUninitializedObject の使用

T instance = (T)FormatterServices.GetUninitializedObject(typeof(T));

FormatterServices.GetUninitializedObject を使用する場合 コンストラクターとフィールド初期化子は呼び出されません。シリアライザとリモーティング エンジンで使用するためのものです

# ジェネリック メソッドを取得して呼び出す

ジェネリック メソッドを持つクラスがあるとします。そして、その関数をリフレクションで呼び出す必要があります。

public class Sample
{
    public void GenericMethod<T>()
    {
        // ...
    }

    public static void StaticMethod<T>()
    {
        //...
    }
}

GenericMethod を string 型で呼び出したいとしましょう。

Sample sample = new Sample();//or you can get an instance via reflection

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(typeof(string));
generic.Invoke(sample, null);//Since there are no arguments, we are passing null

静的メソッドの場合、インスタンスは必要ありません。したがって、最初の引数も null になります。

MethodInfo method = typeof(Sample).GetMethod("StaticMethod");
MethodInfo generic = method.MakeGenericMethod(typeof(string));
generic.Invoke(null, null);

# リフレクションを介してメソッドまたはプロパティへの厳密に型指定されたデリゲートを取得する

パフォーマンスが懸念される場合、リフレクションを介してメソッドを呼び出します (つまり、MethodInfo.Invoke を介して) メソッド) は理想的ではありません。ただし、 Delegate.CreateDelegate を使用して、よりパフォーマンスの高い厳密に型指定されたデリゲートを取得するのは比較的簡単です。 関数。リフレクションを使用することによるパフォーマンスの低下は、デリゲートの作成プロセス中にのみ発生します。デリゲートが作成されると、それを呼び出すことによるパフォーマンスの低下はほとんどまたはまったくありません:

// Get a MethodInfo for the Math.Max(int, int) method...
var maxMethod = typeof(Math).GetMethod("Max", new Type[] { typeof(int), typeof(int) });
// Now get a strongly-typed delegate for Math.Max(int, int)...
var stronglyTypedDelegate = (Func<int, int, int>)Delegate.CreateDelegate(typeof(Func<int, int, int>), null, maxMethod);
// Invoke the Math.Max(int, int) method using the strongly-typed delegate...
Console.WriteLine("Max of 3 and 5 is: {0}", stronglyTypedDelegate(3, 5));

この手法は、プロパティにも拡張できます。 MyClass という名前のクラスがある場合 intMyIntProperty という名前のプロパティ 、厳密に型指定されたゲッターを取得するコードは次のようになります (次の例では、「ターゲット」が MyClass の有効なインスタンスであると想定しています ):

// Get a MethodInfo for the MyClass.MyIntProperty getter...
var theProperty = typeof(MyClass).GetProperty("MyIntProperty");
var theGetter = theProperty.GetGetMethod();
// Now get a strongly-typed delegate for MyIntProperty that can be executed against any MyClass instance...
var stronglyTypedGetter = (Func<MyClass, int>)Delegate.CreateDelegate(typeof(Func<MyClass, int>), theGetter);
// Invoke the MyIntProperty getter against MyClass instance 'target'...
Console.WriteLine("target.MyIntProperty is: {0}", stronglyTypedGetter(target));

...セッターに対しても同じことができます:

// Get a MethodInfo for the MyClass.MyIntProperty setter...
var theProperty = typeof(MyClass).GetProperty("MyIntProperty");
var theSetter = theProperty.GetSetMethod();
// Now get a strongly-typed delegate for MyIntProperty that can be executed against any MyClass instance...
var stronglyTypedSetter = (Action<MyClass, int>)Delegate.CreateDelegate(typeof(Action<MyClass, int>), theSetter);
// Set MyIntProperty to 5...
stronglyTypedSetter(target, 5);

# System.Type を取得

タイプのインスタンスの場合:

var theString = "hello";
var theType = theString.GetType();

タイプ自体から:

var theType = typeof(string);

# プロパティの取得と設定

基本的な使い方:

PropertyInfo prop = myInstance.GetType().GetProperty("myProperty");
// get the value myInstance.myProperty
object value = prop.GetValue(myInstance);

int newValue = 1;
// set the value myInstance.myProperty to newValue
prop.setValue(myInstance, newValue);

読み取り専用の自動実装プロパティの設定は、そのバッキング フィールドを介して行うことができます (.NET Framework では、バッキング フィールドの名前は "k__BackingField" です):

// get backing field info
FieldInfo fieldInfo = myInstance.GetType()
    .GetField("<myProperty>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);

int newValue = 1;
// set the value of myInstance.myProperty backing field to newValue
fieldInfo.SetValue(myInstance, newValue);

# カスタム属性

カスタム属性を持つプロパティを見つける - MyAttribute

var props = t.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | 
            BindingFlags.Instance).Where(
            prop => Attribute.IsDefined(prop, typeof(MyAttribute)));

特定のプロパティのすべてのカスタム属性を検索

var attributes = typeof(t).GetProperty("Name").GetCustomAttributes(false);

カスタム属性を持つすべてのクラスを列挙 - MyAttribute

static IEnumerable<Type> GetTypesWithAttribute(Assembly assembly) {
    foreach(Type type in assembly.GetTypes()) {
        if (type.GetCustomAttributes(typeof(MyAttribute), true).Length > 0) {
            yield return type;
        }
    }
}

実行時にカスタム属性の値を読み取る

public static class AttributeExtensions
{

        /// <summary>
        /// Returns the value of a member attribute for any member in a class.
        ///     (a member is a Field, Property, Method, etc...)    
        /// <remarks>
        /// If there is more than one member of the same name in the class, it will return the first one (this applies to overloaded methods)
        /// </remarks>
        /// <example>
        /// Read System.ComponentModel Description Attribute from method 'MyMethodName' in class 'MyClass': 
        ///     var Attribute = typeof(MyClass).GetAttribute("MyMethodName", (DescriptionAttribute d) => d.Description);
        /// </example>
        /// <param name="type">The class that contains the member as a type</param>
        /// <param name="MemberName">Name of the member in the class</param>
        /// <param name="valueSelector">Attribute type and property to get (will return first instance if there are multiple attributes of the same type)</param>
        /// <param name="inherit">true to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events</param>
        /// </summary>    
        public static TValue GetAttribute<TAttribute, TValue>(this Type type, string MemberName, Func<TAttribute, TValue> valueSelector, bool inherit = false) where TAttribute : Attribute
        {
            var att = type.GetMember(MemberName).FirstOrDefault().GetCustomAttributes(typeof(TAttribute), inherit).FirstOrDefault() as TAttribute;
            if (att != null)
            {
                return valueSelector(att);
            }
            return default(TValue);
        }
    }

使い方

//Read System.ComponentModel Description Attribute from method 'MyMethodName' in class 'MyClass'
var Attribute = typeof(MyClass).GetAttribute("MyMethodName", (DescriptionAttribute d) => d.Description);

# Generic Type のインスタンスを作成し、そのメソッドを呼び出します

var baseType = typeof(List<>);
var genericType = baseType.MakeGenericType(typeof(String));
var instance = Activator.CreateInstance(genericType);
var method = genericType.GetMethod("GetHashCode");
var result = method.Invoke(instance, new object[] { });

# インターフェイスを実装するクラスのインスタンス化 (例:プラグインのアクティベーション)

アプリケーションでプラグイン システムをサポートする場合 (たとえば、plugins にあるアセンブリからプラグインを読み込む場合) フォルダ:

interface IPlugin
{
    string PluginDescription { get; }
    void DoWork();
}

このクラスは別の dll に配置されます

class HelloPlugin : IPlugin
{
    public string PluginDescription => "A plugin that says Hello";
    public void DoWork()
    {
        Console.WriteLine("Hello");
    }
}

アプリケーションのプラグイン ローダーは dll ファイルを見つけ、IPlugin を実装するアセンブリ内のすべての型を取得します。 、それらのインスタンスを作成します。


   public IEnumerable<IPlugin> InstantiatePlugins(string directory)
    {
        var pluginAssemblyNames = Directory.GetFiles(directory, "*.addin.dll").Select(name => new FileInfo(name).FullName).ToArray();
        //load the assemblies into the current AppDomain, so we can instantiate the types later
        foreach (var fileName in pluginAssemblyNames)
            AppDomain.CurrentDomain.Load(File.ReadAllBytes(fileName));
        var assemblies = pluginAssemblyNames.Select(System.Reflection.Assembly.LoadFile);
        var typesInAssembly = assemblies.SelectMany(asm => asm.GetTypes());
        var pluginTypes = typesInAssembly.Where(type => typeof (IPlugin).IsAssignableFrom(type));
        return pluginTypes.Select(Activator.CreateInstance).Cast<IPlugin>(); 
    }

# ジェネリック型のインスタンスのジェネリック引数の決定

ジェネリック型のインスタンスがあるが、何らかの理由で特定の型がわからない場合は、このインスタンスの作成に使用されたジェネリック引数を特定する必要がある場合があります。

誰かが List<T> のインスタンスを作成したとしましょう そのようにしてメソッドに渡します:

var myList = new List<int>();
ShowGenericArguments(myList);

どこで ShowGenericArguments この署名があります:

public void ShowGenericArguments(object o)

したがって、コンパイル時には、o を作成するためにどのジェネリック引数が使用されているかわかりません。 .リフレクションは、ジェネリック型を検査するための多くのメソッドを提供します。最初に、o のタイプかどうかを判断できます。 ジェネリック型です:

public void ShowGenericArguments(object o)
{
    if (o == null) return;

    Type t = o.GetType();
    if (!t.IsGenericType) return;
    ...

Type.IsGenericType true を返します 型がジェネリック型で false の場合

しかし、私たちが知りたいのはこれだけではありません。 List<> それ自体もジェネリック型です。ただし、特定の構築されたジェネリックのインスタンスのみを調べたい 種類。構築されたジェネリック型は、たとえば List<int> です 特定のタイプの引数を持つ すべての一般的なパラメータ .

Type クラスはさらに 2 つのプロパティ IsConstructedGenericType を提供します と IsGenericTypeDefinition 、これらの構築されたジェネリック型をジェネリック型定義から区別するため:

typeof(List<>).IsGenericType // true
typeof(List<>).IsGenericTypeDefinition // true
typeof(List<>).IsConstructedGenericType// false

typeof(List<int>).IsGenericType // true
typeof(List<int>).IsGenericTypeDefinition // false
typeof(List<int>).IsConstructedGenericType// true

インスタンスの一般的な引数を列挙するには、GetGenericArguments() を使用できます Type を返すメソッド ジェネリック型引数を含む配列:

public void ShowGenericArguments(object o)
{
    if (o == null) return;   
    Type t = o.GetType();
    if (!t.IsConstructedGenericType) return;

    foreach(Type genericTypeArgument in t.GetGenericArguments())
        Console.WriteLine(genericTypeArgument.Name);
}

したがって、上記の呼び出し (ShowGenericArguments(myList) ) の結果は次のようになります:

Int32

# 名前空間を指定して Type を取得

これを行うには、型を含むアセンブリへの参照が必要です。必要なアセンブリと同じアセンブリに別の型があることがわかっている場合は、次のようにします。

typeof(KnownType).Assembly.GetType(typeName);

  • `typeName` は探している型の名前 (名前空間を含む) であり、`KnownType` は同じアセンブリ内にあることがわかっている型です。
  • 効率は劣りますが、より一般的な方法は次のとおりです:

    Type t = null;
    foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies())
    {
        if (ass.FullName.StartsWith("System."))
            continue;
        t = ass.GetType(typeName);
        if (t != null)
            break;
    }
    
    

    検索を高速化するために、システム名前空間アセンブリのスキャンを除外するチェックに注意してください。型が実際に CLR 型である可能性がある場合は、これら 2 行を削除する必要があります。

    アセンブリを含む完全にアセンブリ修飾された型名をたまたま持っている場合は、単純にそれを取得できます

    Type.GetType(fullyQualifiedName);
    
    

    # クラスのすべてのプロパティをループする

    Type type = obj.GetType();
    //To restrict return properties. If all properties are required don't provide flag.
    BindingFlags flags = BindingFlags.Public | BindingFlags.Instance; 
    PropertyInfo[] properties = type.GetProperties(flags);
    
    foreach (PropertyInfo property in properties)
    {
        Console.WriteLine("Name: " + property.Name + ", Value: " + property.GetValue(obj, null));
    }
    
    

    # コメント

    リフレクションにより、コードは実行時 (プログラムの実行時) にアセンブリ、モジュール、および型に関する情報にアクセスできます。これをさらに使用して、タイプを動的に作成、変更、またはアクセスできます。タイプには、プロパティ、メソッド、フィールド、および属性が含まれます。

    さらに読む:

    リフレクション(C#)

    .Net Framework でのリフレクション