Ev.
Ev.

Reputation: 7579

Cast a generic with a dynamic Generic Argument At runtime

How do you cast a variable to a generic at runtime, when we have the generic argument as a variable of type "Type":

Here's a little test I knocked up that I'm trying to resolve. It's a contrived example, but illustrates my problem.

using System;
using System.Linq;
using NUnit.Framework;

namespace MyApp.Tests
{
    public interface IMagicConverter<T>
    {
        T ConvertIt(int input);
    }

    /// <summary>
    /// Convert and int to a somewhat related string
    /// </summary>
    public class MyStringConverter : IMagicConverter<string>
    {
        public string ConvertIt(int input)
        {
            switch (input)
            {
                case 1:
                    return "Number one";
                case 2:
                    return "Number two";
                default:
                    return "Another number";
            }
        }
    }
    /// <summary>
    /// Convert a int to a somewhat related date
    /// </summary>
    public class MyDateTimeConverter : IMagicConverter<DateTime>
    {
        public DateTime ConvertIt(int input)
        {
            return DateTime.Today.AddDays(input);
        }
    }

    public class TestClass
    {
        // at this point we know that myConverter implements a IMagicConverter
        public void TestMethod(Type myConverter, int number, ref object returnvalue)
        {
            // work out what the generic argument is
            var typeArgument = myConverter.GetInterfaces()
                                        .Where(x => x.IsGenericType)
                                        .Where(x => x.GetGenericTypeDefinition() == typeof (IMagicConverter<>))
                                        .Select(x => x.GetGenericArguments())
                                        .First();
            // cast it to that type
            var instance = (IMagicConverter<typeArgument>)Activator.CreateInstance(myConverter);
            returnvalue = instance.ConvertIt(number);
        }
    }

    [TestFixture]
    public class MyTest
    {
        [Test]
        public void MyStringConverter_ConvertsTheNumberOne_ToEnglish()
        {
            // Arrange
            var test = new TestClass();
            object returnValue = null;

            // Act
            test.TestMethod(typeof(MyStringConverter), 1, ref returnValue);

            // Assert
            Assert.That(returnValue, Is.EqualTo("Number one"));

        }
    }

Upvotes: 2

Views: 96

Answers (1)

Marc Gravell
Marc Gravell

Reputation: 1064184

Basically, you need to use reflection every step - you'd need to find the method you want via reflection, and invoke that with reflection. Hugely ugly.

At that point, it is tempting to cheat:

public void TestMethod(Type myConverter, int number, ref object returnvalue)
{
    dynamic obj = Activator.CreateInstance(myConverter);
    returnvalue = Evil(obj, number);
}
static T Evil<T>(IMagicConverter<T> converter, int input)
{
    return converter.ConvertIt(input);
}

The trick here is the dynamic, which lets the DLR worry about all of that, with the advantage that it has inbuilt per-type strategy caching, so you only pay any reflection cost once.

The other common approach is to have a non-generic interface, for example:

public interface IMagicConverter
{
    object ConvertIt(int input);
}
public interface IMagicConverter<T> : IMagicConverter
{
    new T ConvertIt(int input);
}

Then you use:

var obj = (IMagicConverter)Activator.CreateInstance(myConverter);
returnvalue = obj.ConvertIt(number);

Here, to satisfy the interfaces, you would also need to add:

object IMagicConverter.ConvertIt(int input)
{
    return ConvertIt(input);
}

to both MyStringConverter and MyDateTimeConverter, to forward the non-generic call to the generic call.

Upvotes: 1

Related Questions