Saturday 1 July 2017

Reflection 01

Reflection gives us the ability to read, modify or invoke behavior from an assembly, module or type; it allows us to inspect and interact with the meta data of our objects. For example our intellicense leverages reflection to let us know what the properties, fields, methods, constructors, attributes, etc.

let's take a look at an implementation of our Person class

[DebuggerDisplay("{DebuggerDisplay()}")]
class Person {
    static int _runningId = 0;
    public int Id { get; } = _runningId++;
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime Birthdate { get; set; }
    public Person() { }
    public Person(string FirstName, string LastName)
        : this()
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
    }
    public Person(string FirstName, string LastName, DateTime Birthdate)
        : this(FirstName, LastName) {
        this.Birthdate = Birthdate;
    }

    public int GetAge() {
        if (Birthdate == new DateTime())
            return -1;

        var today = DateTime.Now;
        var age = today.AddYears(-Birthdate.Year).Year;
        return Birthdate.AddYears(age) > today ? --age : age;
    }

    private string DebuggerDisplay() {
        var age = GetAge();
        var result = $"{FirstName} {LastName}";
        return age > -1 ? $"{result} is {age}" : result;
    }

}

now we can use reflection to  inspect the details of our class

class Program
{
    static void Main(string[] args)
    {
        Type pt = typeof(Person);

        //list attributes
        var attributes = Attribute.GetCustomAttributes(pt);
        foreach (var attr in attributes)
            Console.WriteLine(attr.GetType().Name);

        //list properties
        foreach (var prop in pt.GetProperties())
            Console.WriteLine($"{prop.Name}  {prop.PropertyType}");

        //list constructors
        foreach (var con in pt.GetConstructors())
        {
            Console.WriteLine($"constructor with {con.GetParameters().Length} parameters");
            foreach (var p in con.GetParameters())
                Console.Write($"{p.ParameterType} {p.Name}, ");
            Console.WriteLine("\n");
        }

        //list methods
        foreach (var mi in pt.GetMethods())
            Console.WriteLine(mi.Name);
    }

}

This allows us to see what's inside of our class definition 


We can also inspect at the assembly level

static void Main(string[] args)
{
        var assembly = Assembly.GetExecutingAssembly();
        Console.WriteLine(assembly.FullName);

        foreach (Type t in assembly.GetTypes())
        {
            Console.WriteLine($"Assembly Type: {t.Name}");

            Console.WriteLine("\tConstructors");
            foreach (var con in t.GetConstructors())
            {
                var parCount = con.GetParameters().Length;
                Console.Write($"\t\tconstructor with {parCount} parameters");
                if (parCount == 0)
                {
                    Console.WriteLine();
                    continue;
                }
                Console.Write("\n\t\t\t");
                foreach (var p in con.GetParameters())
                    Console.Write($"{p.ParameterType} {p.Name}, ");
                Console.WriteLine();
            }

            Console.WriteLine("\n\t\tFields");
            foreach (FieldInfo fi in t.GetFields())
                Console.WriteLine($"\t\t\t{fi.Name} ({fi.FieldType})");

            Console.WriteLine("\t\tProperties");
            foreach (PropertyInfo pi in t.GetProperties())
                Console.WriteLine($"\t\t\t{pi.Name} ({pi.PropertyType})");

            Console.WriteLine("\t\tMethods");
            foreach (MethodInfo mi in t.GetMethods())
                Console.WriteLine($"\t\t\t{mi.Name} ({mi.ReturnType})");
        }
    }

}

We can use reflection to call private methods

class Program
{
    static void Main(string[] args)
    {
        var P1 = new Person("John", "Smith", new DateTime(1984, 1, 31));

        MethodInfo debbuggerDisplay = P1.GetType().GetMethod("DebuggerDisplay",
                BindingFlags.NonPublic | BindingFlags.Instance);

        Console.WriteLine(debbuggerDisplay.Invoke(P1, null));
    }

}

by using reflection we can actually get the reference to our persons private DebuggerDisplay function and call it.