Skip to main content

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.

Comments

Popular posts from this blog

async.h - asynchronous, stackless subroutines in C

The async/await idiom is becoming increasingly popular. The first widely used language to include it was C#, and it has now spread into JavaScript and Rust. Now C/C++ programmers don't have to feel left out, because async.h is a header-only library that brings async/await to C! Features: It's 100% portable C. It requires very little state (2 bytes). It's not dependent on an OS. It's a bit simpler to understand than protothreads because the async state is caller-saved rather than callee-saved. #include "async.h" struct async pt; struct timer timer; async example(struct async *pt) { async_begin(pt); while(1) { if(initiate_io()) { timer_start(&timer); await(io_completed() || timer_expired(&timer)); read_data(); } } async_end; } This library is basically a modified version of the idioms found in the Protothreads library by Adam Dunkels, so it's not truly ground bre

Building a Query DSL in C#

I recently built a REST API prototype where one of the endpoints accepted a string representing a filter to apply to a set of results. For instance, for entities with named properties "Foo" and "Bar", a string like "(Foo = 'some string') or (Bar > 99)" would filter out the results where either Bar is less than or equal to 99, or Foo is not "some string". This would translate pretty straightforwardly into a SQL query, but as a masochist I was set on using Google Datastore as the backend, which unfortunately has a limited filtering API : It does not support disjunctions, ie. "OR" clauses. It does not support filtering using inequalities on more than one property. It does not support a not-equal operation. So in this post, I will describe the design which achieves the following goals: A backend-agnostic querying API supporting arbitrary clauses, conjunctions ("AND"), and disjunctions ("OR"). Implemen

Easy Automatic Differentiation in C#

I've recently been researching optimization and automatic differentiation (AD) , and decided to take a crack at distilling its essence in C#. Note that automatic differentiation (AD) is different than numerical differentiation . Math.NET already provides excellent support for numerical differentiation . C# doesn't seem to have many options for automatic differentiation, consisting mainly of an F# library with an interop layer, or paid libraries . Neither of these are suitable for learning how AD works. So here's a simple C# implementation of AD that relies on only two things: C#'s operator overloading, and arrays to represent the derivatives, which I think makes it pretty easy to understand. It's not particularly efficient, but it's simple! See the "Optimizations" section at the end if you want a very efficient specialization of this technique. What is Automatic Differentiation? Simply put, automatic differentiation is a technique for calcu