Casting to less generic types

… because yes, there are valid use cases for it! Finally I found the time to write some unit tests, followed by fixing the remaining bugs, and am now excited to report on the result which effectively allows you to break type safety for generic interfaces, if you so please. Previously I discussed the variance limitations for generics, and how to create delegates which overcome those limitations. Along the same lines I will now demonstrate how to overcome those limitations for entire generic interfaces.

Consider the following interface which is used to check whether a certain value is valid or not.

public interface IValidation<in T>
    bool IsValid( T value );

Notice how T is made contravariant by using the in keyword? Not only values of type T can be validated, but also any extended type. This recent feature of C# won’t help a bit in the following scenario however. During reflection you can only use this interface when you know the complete type, including the generic type parameters. In order to support any type, you would have to check for any possible type and cast to the correct corresponding interface.

object validator;  // An object known to implement IValidation<T>
object toValidate; // The object which can be validated by using the validator.

if ( validator is IValidation<string> )
    IValidation<string> validation = (IValidation<string>)validator;
    validation.IsValid( (string)toValidate );
else if ( validator is IValidation<int> )
    IValidation<int> validation = (IValidation<int>)validator;
    validation.IsValid( (int)toValidate );
else if ...

Hardly entertaining, nor maintainable. What we actually need is covariance in T, or at least something that looks like it. We want to treat a more concrete IValidation<T> as IValidation<object>. For perfectly good reasons covariance is only possible when the type parameter is only used as output, otherwise objects of the wrong type could be passed. In the given scenario however, where we know only the correct type will ever be passed, this shouldn’t be a problem.

The solution is using a proxy class which implements our less generic interface and delegates all calls to the actual instance, doing the required casts where necessary.

public class LessGenericProxy : IValidation<object>
    readonly IValidation<string> _toWrap;

    public LessGenericProxy( IValidation<string> toWrap )
        _toWrap = toWrap;

    public bool IsValid( object value )
        return _toWrap.IsValid( (string)value );

With the power of Reflection.Emit, such classes can be generated at runtime! RunSharp is a great library which makes writing Emit code feel like writing ordinary C#. It’s relatively easy (compared to using Emit) to generate the proxy class. The end result looks as follows:

object validator;  // An object known to implement IValidation<T>
object toValidate; // The object which can be validated by using the validator.

IValidation<object> validation
    = Proxy.CreateGenericInterfaceWrapper<IValidation<object>>( validator );

validation.IsValid( toValidate ); // This works! No need to know about the type.

// Assuming the validator validates strings, this will throw an InvalidCastException.
//validation.IsValid( 10 );

Of course the proxy should be cached when used multiple times. Originally I also attempted to proxy classes instead of just interfaces by extending from them. This only works properly for virtual methods. Since non-virtual methods can’t be overridden there is no way to redirect the calls to the required inner instance.

Source code can be found in my library: Whathecode.System.Reflection.Emit.Proxy.

  1. Non-generic Wrapper instead of Base Class or Interface |

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: