使用动态通用参数在运行时强制转换泛型

时间:2014-02-20 07:50:15

标签: c# generics reflection

当我们将泛型参数作为“Type”类型的变量时,如何在运行时将变量强制转换为泛型:

这是我试图解决的一个小试验。这是一个人为的例子,但说明了我的问题。

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"));

        }
    }

1 个答案:

答案 0 :(得分:1)

基本上,你需要在每一步都使用反射 - 你需要通过反射找到你想要的方法,并用反射来调用它。非常难看。

在那一点上,它很容易作弊:

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);
}

这里的诀窍是dynamic,它让DLR担心所有这些,其优势在于它具有内置的每类型策略缓存,因此您只需支付一次任何反射成本。

另一种常见方法是使用非通用接口,例如:

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

然后你使用:

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

在这里,为了满足接口,您还需要添加:

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

同时MyStringConverterMyDateTimeConverter,将非通用调用转发给通用调用。