サーチ…
前書き
リフレクションは、実行時に動的オブジェクトのプロパティにアクセスするためのC#言語メカニズムです。通常、リフレクションは、動的オブジェクトタイプおよびオブジェクト属性値に関する情報を取得するために使用されます。たとえば、RESTアプリケーションでは、リフレクションを使用して、シリアライズされたレスポンス・オブジェクトを反復することができます。
備考:MSのガイドラインによれば、パフォーマンスの重要なコードは反映されるべきではありません。 https://msdn.microsoft.com/en-us/library/ff647790.aspxを参照してください。
備考
リフレクションを使用すると、実行時にアセンブリ、モジュール、およびタイプに関する情報にコードがアクセスできるようになります(プログラム実行)。これをさらに使用して、タイプを動的に作成、変更、またはアクセスすることができます。タイプには、プロパティ、メソッド、フィールド、および属性が含まれます。
参考文献 :
System.Typeを取得する
型のインスタンスの場合:
var theString = "hello";
var theType = theString.GetType();
型自体から:
var theType = typeof(string);
型のメンバーを取得する
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.");
}
}
}
アウトプット(アウトプットオーダについての注意事項をさらに見てください ):
GetType is a Method, it has not been inherited. GetHashCode is a Method, it has not been inherited. ToString is a Method, it has not been inherited. Equals is a Method, it has not been inherited. Equals is a Method, it has not been inherited. ReferenceEquals is a Method, it has not been inherited. .ctor is a Constructor, it has not been inherited.
また、 BindingFlags
を渡さずにGetMembers()
を使用することもできます。これは、その特定のタイプのすべてのパブリックメンバーを返します 。
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
プロパティの取得と設定
基本的な使用法:
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);
クラスのすべてのプロパティをループする
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));
}
ジェネリック型のインスタンスの汎用引数の決定
ジェネリック型のインスタンスがありますが、なんらかの理由で特定の型がわからない場合は、このインスタンスの作成に使用された汎用の引数を調べることをお勧めします。
たとえば、誰かがList<T>
インスタンスを作成し、それをメソッドに渡すとしましょう:
var myList = new List<int>();
ShowGenericArguments(myList);
ShowGenericArguments
はこのシグネチャがあります。
public void ShowGenericArguments(object o)
コンパイル時に、 o
を作成するために一般的な引数が使われているかどうかはわかりません。 Reflectionは、ジェネリック型を検査するための多くのメソッドを提供します。最初に、 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
インスタンスの汎用引数を列挙するために、ジェネリック型引数を含むType
配列を返すGetGenericArguments()
メソッドを使用できます。
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
ジェネリックメソッドを取得して呼び出す
ジェネリックメソッドを持つクラスがあるとしましょう。そして、その機能をリフレクションで呼び出す必要があります。
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);
汎用型のインスタンスを作成し、それを呼び出す
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>();
}
型のインスタンスの作成
最も簡単な方法は、 Activator
クラスを使用することです。
しかし、 Activator.CreateInstance()
を使用して、 Activator
パフォーマンスが.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;
}
呼び出しメソッドの使用
// 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以上の演算子のシーケンスであり、単一の値、オブジェクト、メソッド、または名前空間に評価されます。式は、リテラル値、メソッド呼び出し、演算子とそのオペランド、または単純な名前で構成できます。シンプルな名前には、変数名、型メンバー、メソッドパラメーター、名前空間または型を指定できます。
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
コンストラクタを使用する場合、フィールド初期化子は呼び出されません。シリアライザやリモーティングエンジンで使用されることを意図しています
ネームスペースで名前で型を取得する
これを行うには、型を含むアセンブリへの参照が必要です。必要なものと同じアセンブリ内にあることがわかっている別のタイプがある場合は、これを行うことができます:
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);
リフレクションを介してメソッドまたはプロパティに強く型付けされたデリゲートを取得する
パフォーマンスが懸念される場合、リフレクション(つまり、 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));
この手法はプロパティにも拡張できます。 MyIntProperty
という名前のint
プロパティを持つMyClass
というクラスがある場合、強く型付けされたgetterを取得するコードは次のようになります(次の例では、 'target'は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);