Saturday, March 23, 2013

Sasa.Types - Runtime Types And CLR Metadata

This is the eighth post in my ongoing series covering the abstractions in Sasa. Previous posts:

Sasa.Types is a static class containing a number of extension methods on System.Type, together with extensions that mirror some of the CLR metadata instructions which aren't typically available in C#. It's available in the core Sasa.dll.

Sasa.Types.Constructor

Sasa.Types.Constructor accepts a lambda expression with a "new" expression designating a type's constructor. It then extracts and returns the ConstructorInfo used that expression:

struct Foo
{
   public Foo(int i)
   {
     ...
   }
}
...
var x = Types.Constructor(() => new Foo(3));
Console.WriteLine(x);
// output:
// Void .ctor(Int32)

Sasa.Types.Create

Sasa.Types.Create is a static method used to create a dynamic type in a dynamic assembly, often for code generation purposes. It automates various steps and provides a boolean parameter indicating whether to save the assembly to a file, so you can run verification passes on it:

var newType = Types.Create("TypeFoo",
                           saveAssembly:true,
                           generate: typeBuilder =>
{
    // see Microsoft's docs on TypeBuilder:
    // http://msdn.microsoft.com/en-us/library/system.reflection.emit.typebuilder.aspx
    ...
});

Sasa.Types.Field

Sasa.Types.Field are a set of extension methods used to extract FieldInfo metadata from a simple member access expression for that field:

struct Foo
{
   public int Instance;
   public static string Static;
}
...
var instanceField = Types.Field<Foo, int>(x => x.Instance);
var staticField = Types.Field<int>(() => Foo.Static);
Console.WriteLine(instanceField);
Console.WriteLine(staticField);
// output:
// Int32 Instance
// String Static

The first overload is for static field, and the second overload is for instance fields since it accepts an expression taking an instance and returning the field value.

The one caveat is that the C# can't enforce that you properly reference fields, or that you're using the write overload to access static vs. instance fields. Instance fields require an instance in order to reference them, so you must use the overload that accepts two generic arguments. Static fields only require the use of one generic argument.

Sasa.Types.FieldName

Sasa.Types.FieldName is an extension method on FieldInfo that extracts a "normalized" field name. By "normalized", I mean that if the field was a compiler-generated backing field for an auto property, then it will extract the property name. Otherwise, it will just return the field name itself:

public class Foo
{
    public int normalField;
    public int AutoProperty { get; set; }
}
var backingField = Types.Property<Foo, int>(x => x.AutoProperty)
                        .GetBackingField();
var normalField = Types.Field<Foo, int>(x => x.normalField);

Console.WriteLine(backingField.FieldName());
Console.WriteLine(normalField.FieldName());
// output:
// AutoProperty
// normalField

Sasa.Types.GetBackingField

Sasa.Types.GetBackingField is an extension method on PropertyInfo that attempts to extract the compiler-generated field metadata:

public class Foo
{
    public int AutoProperty { get; set; }
}
var backingField = Types.Property<Foo, int>(x => x.AutoProperty)
                        .GetBackingField();
Console.WriteLine(backingField.Name.FieldName());
Console.WriteLine(backingField.Name);
// output:
// AutoProperty
// <AutoProperty>k__BackingField

Note that this method currently depends on the naming convention used by the compiler, so it may not be 100% future-proof. If the convention ever does change, I anticipate updating this implementation to reflect that.

Sasa.Types.GetTypeTree

Sasa.Types.GetTypeTree is an extension method on System.Type that extracts the whole sequence of generic type instances, declarations and arguments that make up a type:

var args = typeof(Pair<int, Pair<string,char>>).GetTypeTree();
// args = new[]
// {
//     typeof(Pair<int, Pair<string, char>>),
//     typeof(Pair<,>),
//          typeof(int),
//          typeof(Pair<string, char>),
//          typeof(Pair<,>),
//              typeof(string), typeof(char)
// };

Sasa.Types.HasAutoField

Sasa.Types.HasAutoField is an extension method on PropertyInfo that checks whether a property is an auto-property with a compiler-generated backing field:

class Foo
{
    public int Bar
    {
        get { return 0; }
    }
    public int AutoProp { get; set; }
}
var autop = Types.Property<Foo, int>(x => x.AutoProp);
var normp = Types.Property<Foo, int>(x => x.Bar);

Console.WriteLine(autop.HasAutoField());
Console.WriteLine(normp.HasAutoField());
// output:
// true
// false

Sasa.Types.IsBackingField

Sasa.Types.IsBackingField is an extension method on FieldInfo that checks whether a field is a compiler-generated backing field for a property:

public class Foo
{
    public int normalField;
    public int AutoProperty { get; set; }
}
var backingField = Types.Property<Foo, int>(x => x.AutoProperty)
                        .GetBackingField();
var normalField = Types.Field<Foo, int>(x => x.normalField);

Console.WriteLine(backingField.IsBackingField());
Console.WriteLine(normalField.IsBackingField());
// output:
// true
// false

Sasa.Types.IsGenericTypeInstance

Sasa.Types.IsGenericTypeInstance is an extension method on System.Type that checks whether the Type instance is an instantiated generic type definition:

struct Foo { }
struct Foo<T> { }

Console.WriteLine(typeof(Foo).IsGenericTypeInstance());
Console.WriteLine(typeof(Foo<>).IsGenericTypeInstance());
Console.WriteLine(typeof(Foo<int>).IsGenericTypeInstance());

// output:
// false
// false
// true

Sasa.Types.Method

Sasa.Types.Method is a static method that extracts the MethodInfo of a delegate expression:

struct Foo
{
   public static int DoSomething(string x);
   public int Otherwise();
}
...
var doSomething = Types.Method<Func<string, int>>(() => Foo.DoSomething);
var otherwise = Types.Method<Func<Foo, Func<int>>>(x => x.Otherwise);

Console.WriteLine(doSomething);
Console.WriteLine(otherwise);

// output:
// Int32 DoSomething(String)
// Int32 Otherwise()

The second overload is for instance methods, so it accepts an expression that takes an instance, and returns a delegate to the method of interest.

Sasa.Types.Property

Sasa.Types.Property are a set of extension methods that extract the PropertyInfo of a property access expression:

public class Foo
{
    public static int StaticProperty { get; set; }
    public int AutoProperty { get; set; }
}
var sprop = Types.Property(() => Foo.StaticProperty);
var iprop = Types.Property<Foo, int>(x => x.AutoProperty);

Console.WriteLine(sprop);
Console.WriteLine(iprop);
// output:
// Int32 StaticProperty
// Int32 AutoProperty

The first overload is for static properties, and the second overload is for instance properties since it accepts an expression taking an instance and returning the property value.

Sasa.Types.ShortGenericType

Sasa.Types.ShortGenericType is a set of extension methods on System.Type that generates an abbreviated string describing a generic type, ie. assembly references omit versioning and public key information:

var nullableInt = typeof(int?);
var nullable = nullableInt.GetGenericTypeDefinition();
Console.WriteLine(nullable.ShortGenericType(nullableInt.GetGenericArguments()));
// output:
// [System.Nullable`1[[System.Int32, mscorlib]], mscorlib]]

Sasa.Types.ShortName

Sasa.Types.ShortName is a set of extension methods on System.Type that generates an abbreviated string describing any type, including generic types:

Console.WriteLine(typeof(int?).ShortName());
// output:
// [System.Nullable`[[System.Int32, mscorlib]], mscorlib]

Sasa.Types.Subtypes

Sasa.Types.Subtypes is a set of extension methods on System.Type that check subtyping relationships on runtime types and type arguments:

Console.WriteLine(typeof(int).Subtypes(typeof(object)));
Console.WriteLine(typeof(int).Subtypes<object>());
Console.WriteLine(typeof(int).Subtypes<string>());

// output:
// true
// true
// false

However, this check has an important limitation when dealing with type parameters. See Type.IsAssignableFrom.

No comments: