[C x.net addendum] 04: reflections you have to know

Keywords: Attribute shell

It will take about three minutes to read this article.

In general, reflection is used to dynamically obtain information such as object types, properties, and methods. Today, I'll take you to play with reflection to summarize various common operations of reflection and find out if there's anything you don't know.

Get members of type

The GetMembers method of the Type class is used to get all members of the Type, including methods and properties, which can be filtered by the BindingFlags flag flag.

using System;
using System.Reflection;
using System.Linq;

public class Program
{
    public static voidMain()
    {
        var members = typeof(object).GetMembers(BindingFlags.Public |
            BindingFlags.Static | BindingFlags.Instance);
        foreach (var member in members)
        {
            Console.WriteLine($"{member.Name} is a {member.MemberType}");
        }
    }
}

Output:

GetType is a Method
GetHashCode is a Method
ToString is a Method
Equals is a Method
ReferenceEquals is a Method
.ctor is a Constructor

The GetMembers method can also not be passed to BindingFlags. By default, all the public members are returned.

Get and call methods of objects

The GetMethod method method of Type is used to get MethodInfo of this Type, which can be called dynamically through MethodInfo.

For non static methods, you need to pass the corresponding instance as a parameter. For example:

class Program
{
    public static void Main()
    {
        var str = "hello";
        var method = str.GetType()
            .GetMethod("Substring", new[] {typeof(int), typeof(int)});
        var result = method.Invoke(str, new object[] {0, 4}); // amount to str.Substring(0, 4)
        Console.WriteLine(result); // Output: shell
    }
}

For a static method, the object parameter is null. For example:

var method = typeof(Math).GetMethod("Exp");
// amount to Math.Exp(2)
var result = method.Invoke(null, new object[] {2});
Console.WriteLine(result); // Output (e^2): 7.38905609893065

If it is a generic method, you also need to create a generic method through generic parameters. For example:

class Program
{
    public static void Main()
    {
        // Reflection calls generic methods
        MethodInfo method1 = typeof(Sample).GetMethod("GenericMethod");
        MethodInfo generic1 = method1.MakeGenericMethod(typeof(string));
        generic1.Invoke(sample, null);

        // Reflection calls static generic methods
        MethodInfo method2 = typeof(Sample).GetMethod("StaticMethod");
        MethodInfo generic2 = method2.MakeGenericMethod(typeof(string));
        generic2.Invoke(null, null);
    }
}

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

Create an instance of type

There are many ways to dynamically create an instance of a type using reflection. The simplest is to declare with a new() condition.

Use new condition declaration

If you need to create an instance dynamically within a method, you can directly use the new condition declaration, for example:

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

However, this method can be used in limited scenarios, for example, it is not suitable for types with parameters in constructors.

Using the Activator class

Using the Activator class to dynamically create an instance of a class is the most common method. For example:

Type type = typeof(BigInteger);
object result = Activator.CreateInstance(type);
Console.WriteLine(result); // Output: 0
result = Activator.CreateInstance(type, 123);
Console.WriteLine(result); // Output: 123

To dynamically create a generic instance, you need to first create an open generic (such as list < > and then convert it to a concrete generic (such as list < string >) according to the generic parameters. For example:

// Create open generics first
Type openType = typeof(List<>);
// Recreate concrete generics
Type[] tArgs = { typeof(string) };
Type target = openType.MakeGenericType(tArgs);
// Finally, create a generic instance
List<string> result = (List<string>)Activator.CreateInstance(target);

If you don't know what open generics and concrete generics are, see the last section of this article.

Use constructor reflection

You can also create an instance of a class dynamically by reflecting the constructor, which is a little more cumbersome than using the Activator class above, but with better performance. Example:

ConstructorInfo c = typeof(T).GetConstructor(new[] { typeof(string) });
if (c == null)
    throw new InvalidOperationException("...");
T instance = (T)c.Invoke(new object[] { "test" });

Use FormatterServices class

If you want to create an instance of a class without performing constructor and property initialization, you can use the GetUninitializedObject method of FormatterServices. Example:

class Program
{
    static void Main()
    {
        MyClass instance = (MyClass)FormatterServices.GetUninitializedObject(typeof(MyClass));
        Console.WriteLine(instance.MyProperty1); // Output: 0
        Console.WriteLine(instance.MyProperty2); // Output: 0
    }
}

public class MyClass
{
    public MyClass(int val)
    {
        MyProperty1 = val < 1 ? 1 : val;
    }

    public int MyProperty1 { get; }

    public int MyProperty2 { get; set; } = 2;
}

Get a strongly typed delegate for a property or method

After getting the properties and methods of an object through reflection, if you want to access or call them through strongly typed methods, you can add a layer of delegation in the middle. The advantage of this is to facilitate encapsulation, and the caller can clearly know what parameters need to be passed when calling. For example, this method Math.Max Method is extracted as a strongly typed delegate:

var tArgs = new Type[] { typeof(int), typeof(int) };
var maxMethod = typeof(Math).GetMethod("Max", tArgs);
var strongTypeDelegate = (Func<int, int, int>)Delegate
    .CreateDelegate(typeof(Func<int, int, int>), null, maxMethod);
Console.WriteLine("3 The largest between and 5 are:{0}", strongTypeDelegate(3, 5)); // Output: 5

This technique is also applicable to properties. You can get getters and setters of strong types. Example:

var theProperty = typeof(MyClass).GetProperty("MyIntProperty");

// Strong Getter
var theGetter = theProperty.GetGetMethod();
var strongTypeGetter = (Func<MyClass, int>)Delegate
    .CreateDelegate(typeof(Func<MyClass, int>), theGetter);
var intVal = strongTypeGetter(target); // About: target.MyIntProperty

// Strong type Setter
var theSetter = theProperty.GetSetMethod();
var strongTypeSetter = (Action<MyClass, int>)Delegate
    .CreateDelegate(typeof(Action<MyClass, int>), theSetter);
strongTypeSetter(target, 5); // amount to: target.MyIntProperty = 5

Reflection get custom properties

Here are four common scenario examples.

For example 1, find out the attribute in a class marked with a custom attribute (such as MyAtrribute).

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

For example 2, find out all the custom properties of a property.

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

In example 3, find out all classes in the assembly that have a custom property labeled.

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

Example 4, reading the value of a custom property at run time

public static class AttributeExtensions
{
    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;
    }
}

// use:

class Program
{
    static void Main()
    {
        // Read the value of the Description attribute of the MyMethod method method of the MyClass class
        var description = typeof(MyClass)
            .GetAttribute("MyMethod", (DescriptionAttribute d) => d.Description);
        Console.WriteLine(description); // Output: Hello
    }
}
public class MyClass
{
    [Description("Hello")]
    public void MyMethod() { }
}

Dynamically instantiate all implementation classes of an interface (plug-in activation)

All implementation classes of an interface are instantiated dynamically through reflection, which is often used to implement plug-in development of the system. For example, when the program starts, read the dll file in the specified folder (such as Plugins), obtain all classes in the dll that implement an interface through reflection, and instantiate them when appropriate. The implementation is as follows:

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

A class in a standalone dll:

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

When your system starts, load the dll dynamically, read the information of all classes that implement the IPlugin interface, and instantiate it.

public IEnumerable<IPlugin> InstantiatePlugins(string directory)
{
    var assemblyNames = Directory.GetFiles(directory, "*.addin.dll")
        .Select(name => new FileInfo(name).FullName).ToArray();

    foreach (var fileName assemblyNames)
        AppDomain.CurrentDomain.Load(File.ReadAllBytes(fileName));

    var assemblies = assemblyNames.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>();
}

Check generic parameters of generic instances

The construction of generics and concrete generics are mentioned in the previous article, which will be explained here. Most of the time we call generics refer to constructing generics, sometimes referred to as concrete generics. For example, list < int > is a construction generic, because it can be instantiated through new. Accordingly, list < > generics are non constructed generics, sometimes referred to as open generics, which cannot be instantiated. Open generics can be converted into arbitrary concrete generics by reflection, as shown in the previous examples.

If there is a generic instance now, for some reason, we want to know what generic parameters are needed to build this generic instance. For example, someone created an instance of list < T > generics and passed it as a parameter to one of our methods:

var myList = newList<int>();
ShowGenericArguments(myList);

Our method signature is as follows:

public void ShowGenericArguments(object o)

At this time, as the author of this method, we do not know what type of generic parameters this o object is built with. Through reflection, we can get a lot of information about generic instances, the simplest of which is to determine whether a type is generic or not

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

Since list < > itself is a generic Type, the above judgment is not rigorous. What we need to know is whether the object is a constructed generic Type (list < int >). The Type class also provides some useful properties:

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

IsConstructedGenericType and IsGenericTypeDefinition are used to determine whether a generic is a constructed generic or not.

Combined with the GetGenericArguments() method of Type, you can easily know what generic parameters a generic instance is built with, for example:

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

The above is about the reflection of the dry goods knowledge, are summed up from the actual project development, hope to help your development.

Posted by shalinik on Thu, 11 Jun 2020 01:13:22 -0700