수색…
소개
리플렉션은 런타임에 동적 객체 속성에 액세스하기위한 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);
자동으로 구현되는 읽기 전용 속성 설정은 Backing 필드를 통해 수행 할 수 있습니다 (Backing 필드의 .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<>
자체도 제네릭 형식입니다. 그러나 우리는 특정 생성 된 generic 형식의 인스턴스 만 검사하려고합니다. 생성 된 제네릭 형식은 예를 들어 모든 일반 매개 변수에 대한 특정 형식 인수가 있는 List<int>
입니다.
Type
클래스는 생성 된 제네릭 형식을 제네릭 형식 정의와 구별하기 위해 IsConstructedGenericType
및 IsGenericTypeDefinition
IsConstructedGenericType
두 가지 속성을 더 제공합니다.
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
폴더에있는 어셈블리에서 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()
사용하여 .NET 3.5 이후 Activator
성능이 향상되었지만 (상대적으로) 낮은 성능으로 인해 때때로 테스트 옵션 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에서 설명한대로 :
표현식은 하나 이상의 피연산자와 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 유형일 수 있으면이 두 행을 삭제해야합니다.
어셈블리를 포함하여 어셈블리가 정규화 된 형식 이름을 사용하면 간단히 얻을 수 있습니다.
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);