TinyReflectiveToolkit

Structural contracts, eager assembly loading, easier reflection.

View project on GitHub

TinyReflectiveToolkit is a small C# library that offers some cool reflection-related facilities. It's still a very early version and a stable API is far, far away.

It is written and maintained by Theodoros Chatzigiannakis. It is distributed as free software, covered by the Mozilla Public License v2.0.

Runtime Contracts, Part 1: Implementing .NET interfaces at runtime

When working with C# (or any other language with static and nominal typing), you may occasionally end up in a situation where you have two or more types that both expose one or more compatible methods:

public class UnrelatedType1 
{
    public int GetValue() { return 1; }
}

public class UnrelatedType2
{
    public int GetValue() { return 2; }
}

For some reason, you may want to put instances of these types in a common variable or collection and call GetValue() on them, but you can't, because they don't have a declared relationship that would allow the substitution you want:

var values = new object[] 
{
   new UnrelatedType1(),
   new UnrelatedType2()
};
var sum = values.Sum(x => x.GetValue());      // no GetValue() member

Usually, the solution is to define an interface that you know they already implement in practice:

public interface IValue 
{
    int GetValue();
}

And then go ahead and add that interface to the list of interfaces these types implement. But often you can't do that because at least one of these types comes from a 3rd party library/framework and you don't have access to its code (or it may be impractical to change it and rebuild).

Most likely, you end up in the frustrating situation of having to use reflection or employ dynamic typing.

Reflection has disadvantages:

  1. You have to cast the instances to a common supertype (like object) that's not useful for you and in the process you lose compile-time type safety.
  2. The call spots are ugly, in the sense that they are littered with typeof, GetType() and Invoke and possibly more casting and boxing.
  3. Member access using reflection is often slower than access through a statically typed expression (even if that call is a virtual call through an interface).

Dynamic typing has disadvantages:

  1. You have to use a dynamic variable and lose compile-time type safety.
  2. When you're using a dynamic expression, you lose IntelliSense!
  3. Member access using dynamic expressions is often slower than access through a statically typed expression (as above).

Using this very library, you have a third way to solve this problem - so don't throw away that type-safe interface you declared earlier! Find an object whose runtime type satisfies your interface, simply call .ToContract<T>() where T is your interface and you're done!

With the example types declared above, this becomes:

var values = new IValue[]
{
   new UnrelatedType1().ToContract<IValue>(),
   new UnrelatedType2().ToContract<IValue>() 
};
var sum = values.Sum(x => x.GetValue());

And it works!

This techinque has some important advantages:

  1. The syntax is clean and natural C#.
  2. IntelliSense doesn't go away at all and neither does type safety at the interface call spots.
  3. When calling through the interface, the actual methods are stubs implemented using IL - the interface doesn't use reflection and lookups.
  4. It's extremely easy to use: just define your interface, call an extension method on those objects and let things take care of themselves automatically!

Of course, it also has some disadvantages and limitations:

  1. The ToContract<T>() call can be expensive, because it uses reflection (to verify compatibility for each type/interface combination) and it emits IL dynamically to generate proxy types. However, this is largely mitigated by the fact that each type/interface combination is cached and existing proxy types are reused if they are applicable.
  2. The ToContract<T>() call can only verify compatibility at runtime, not compile time.
  3. The runtime type of the passed object must be declared public.
  4. The interface T must also be public.

Runtime Contracts, Part 2: Covariance and contravariance in return types and parameter types

Consider the following class:

public class MyClass
{
    public string Method(object arg)
    {
        return arg.ToString();
    }
}

And consider the following interface:

public interface IObjectToString
{
    string Method(object arg);
}

It's easy to see why MyClass matches the constraints set by the IObjectToString interface. So you can legally declare MyClass : IObjectToString.

There are, however, other variations of this interface that the class doesn't seem to match:

public interface IObjectToObject
{
    object Method(object arg);
}
public interface IStringToString
{
    string Method(string arg);
}
public interface IStringToObject
{
    object Method(string arg);
}

Indeed, any attempt to declare MyClass : IObjectToObject or MyClass : IStringToString or MyClass : IStringToObject is a compile-time error. This is how the C# language is.

But this is just a design decision or a limitation. It doesn't have to be like this.

If the interface requires that the method returns any kind of object, then a method that returns a string (which is a kind of object) actually satisfies the contract. In other words, return types could be covariant in inheritance scenarios.

Similarly, if an interface requires that a method takes a string, then a method that can take any object (including a string) actually satisfies the contract as well. In other words, parameter types could be contravariant in inheritance scenarios.

If you can see why this is so, then you'll be happy to know that this library supports covariance and contravariance (for method return types and parameter types respectively). In other words, the class shown above actually satisfies all four interfaces/contracts that were presented, using the .Satisfies() and .ToContract() extension methods.

The careful reader will notice that even where C# does support covariance and contravariance, it's applicable only for reference types. Or, with an example: an IEnumerable<string> is an IEnumerable<object> but an IEnumerable<int> isn't an IEnumerable<object>. This is because unboxed value types aren't proper Liskov subtypes of object.

This same careful reader will be happy to know that this library supports all of the above even for value types. In other words, what we described above between string and object works the same between int and object as well.

Runtime Contracts, Part 3: Binding to static methods and operators

Normal .NET interfaces are designed to only declare instanced public properties and methods. This means that an interface can't describe the existence of static methods, operators, conversions, etc. This usually makes sense from a design viewpoint, but there are times when it limits the expressiveness of the constraints.

The runtime contracts offered in this library are described as .NET interfaces, but they can bind to things like static methods and operators, using some attributes.

Example for static methods:

public interface IParsable
{
    [Static]
    object Parse(string s);
}

The [Static] attribute means that the candidate types must have a static method with that signature. If they do, then they satisfy this contract.

Therefore, the types bool, int, double satisfy the IParsable contract.

Whenever these types are accessed through this contract, a Parse(string) method will be available and it will polymorphically invoke the correct static method from that particular type:

var ten = typeof(int).ToStaticContract<IParsable>().Parse("10");

Example for operators:

public interface ICastableTo<T>
{
    [Cast]
    T PerformCast();
}

The [Cast] attribute means that the candidate types are not required to implement a PerformCast() method. Instead, they are required to contain an explicit conversion operator to T. If they do, then they satisfy this contract. (The name of the decorated method is completely irrelevant.)

For example, instances of int, float, double, decimal satisfy the ICastableTo<int> contract.

With that out of the way, whenever instances of these types are accessed through this contract, a PerformCast() method will be available and it will polymorphically invoke the correct cast operator for the runtime type of the instance it is called on.

For binary operators, you can pick which side you want your current object on, like this:

public interface ISubtractable
{
    [Subtraction(OpSide.ThisLeft)]
    int Subtract(int p);

    [Subtraction(OpSide.ThisRight)]
    int SubtractFrom(int p);
}

In this case, for a type T, Subtract binds to operator- (T, int) (this goes left) while SubtractFrom binds to operator- (int, T) (this goes right). There is currently no support for binary operators of the form operator- (T, T).

AssemblyLoader: Loading dependencies eagerly

By default, your runtime may be loading your application's dependencies lazily - that is, just before the first time you attempt to use them. This is good for applications that value short startup time and can handle small, distributed performance hits along the way.

But there are scenarios in which eager loading (even with a longer startup time) is preferred over incremental but possibly unpredictable lazy loading of dependencies. (A game is an example of an application that might fall into this category.)

The AssemblyLoader class gives you the opportunity to eagerly load pending dependencies (and their dependencies, in turn) at any specified point. It's easy to use:

new AssemblyLoader().LoadAllDependencies();

It exposes events to which you can subscribe and be alerted between loading dependencies - it's great for (say) creating informative splash screens for your application!

var loader = new AssemblyLoader();
loader.AssemblyLoading += (assembly) =>
{
    Application.Invoke((a, b) => splash.LoadingText = assembly.Name);
    Task.Delay(1).Wait();
};
loader.LoadAllDependencies();

Extension methods for reflection types and collections

Some built-in reflection-related methods aren't as easy to use as they could be. Some slightly more humane extension methods are offered, to make life a little bit easier:

Find all things with a specified attribute:

var tests = allTypes.WithAttribute<TestFixtureAttribute>();

Effortlessly apply a filter to that attribute, while still returning the members you want:

var thoseTests = allMethods
    .WithAttribute<TestAttribute>(x => x.Description == "That test");

...and more stuff will be added.