运行时设置泛型类型

49
我有一个类
public class A<T>
{
   public static string B(T obj)
   {
       return TransformThisObjectToAString(obj);
   }
}

上面的字符串仅仅是举例说明。我可以在已知/指定的类型上这样轻松调用静态函数:

string s= A<KnownType>.B(objectOfKnownType);

如果我不知道T的值,而是有一个类型为Type的变量来保存类型,我该如何进行调用呢?如果我这样做:

Type t= typeof(string);
string s= A<t>.B(someStringObject);

我收到了这个编译器错误:
Cannot implicitly convert type 't' to 'object'

6个回答

48

你不能直接这样做,但是你可以使用反射在运行时提供一个类的类型参数。我没有测试过这个方法,但是像下面这样应该可以:

// We want to do something like this:
//    object o = "Hello"
//    Type t = o.GetType(); 
//
// This is pseudo-code only:
//    string s = A<t>.B(o); 

string InvokeA(object o) {
  // Specify the type parameter of the A<> type
  Type genericType = typeof(A<>).MakeGenericType(new Type[] { o.GetType() });
  // Get the 'B' method and invoke it:
  object res = genericType.GetMethod("B").Invoke(new object[] { o });
  // Convert the result to string & return it
  return (string)res;
}

当然,问题在于你是否真的需要这个东西——如果你对所给的对象一无所知,那么你也可以只使用对象编写整个代码。不过,我可以想象出一些情景下这将会很有用,所以你可以尝试使用这个。


哦,我没有意识到你可以将 A<> 传递给 typeof()。你的代码绝对比我的更简洁。干得好。 - Dathan
我并不是很喜欢在C#中可以写A<>,因为它并不是一个真正的类型。这有点奇怪。不过,我猜有时候它还是有用的 :-). - Tomas Petricek
编写A <>和类似Outer<,>.Inner<,,,>的内容对于typeof“参数”非常有用,但是在其他情况下不允许它是一件好事,因为您确实没有一个C#类型。 - Jeppe Stig Nielsen
A<> 是一个不完整的类型。我在工厂的上下文中使用了这样的结构,其中我正在寻找从通用基类继承的类型,使用CRTP。我将从程序集中加载所有类型,查看是否可以在类型上实例化通用基类,然后查看类型是否继承自实例化。如果是,则是我的工厂可以创建的类型之一。 - Craig

18

框架和CLR完全支持这一点,只是在C#中不太优雅。 不过,通过使用帮助方法,您可以实现您想要的功能:

public class A<T>
{
    public static string B(T obj)
    {
        return obj.ToString();
    }
}

public class MyClass
{
    public static void DoExample()
    {
        Console.WriteLine(ExecuteB("Hi"));
        Console.WriteLine(ExecuteB(DateTime.Now));
    }

    public static object ExecuteB(object arg)
    {
        Type arg_type = arg.GetType();
        Type class_type = typeof(MyClass);
        MethodInfo mi = class_type.GetMethod("ExecuteBGeneric", BindingFlags.Static | BindingFlags.Public);
        MethodInfo mi2 = mi.MakeGenericMethod(new Type[] { arg_type });
        return mi2.Invoke(null, new object[] { arg });
    }

    public static object ExecuteBGeneric<T>(T arg)
    {
        return A<T>.B(arg);
    }

15

你无法实现。泛型类型标识符必须在编译时已知。

编辑

根据其他帖子,似乎可以通过动态生成方法并调用它来实现 - 当然存在危险性。有关更多信息,请参见 Thomas 和 Dathan 的帖子。


我想我误读了问题。除了你已经说的,我真的无法再添加任何东西了。 - ChaosPandion
7
更准确的说法是,没有一种静态类型的方法来做到这一点;或者可能没有惯用的方式。但正如Tomas和我的回答所显示的那样,该方法可以在运行时动态解决,并使用任意参数进行调用。 - Dathan

4
你不能这样做。但是你针对所提供的情况问错了问题。在这种情况下(就像99%的情况一样),实际上你只需要一个类型约束

尝试:

public class A<T> where T : object

或者,如果T是一个已知的类、子类或接口,则最好使用。
public class A<T> where T : YourAbstractClass

还有其他类型的约束条件存在。更多细节请参见:http://msdn.microsoft.com/en-us/library/d5x73970(VS.80).aspx

一般来说,当学习一门新语言时,你通常需要广泛思考你想要实现的目标,而不是具体寻找你想要做的事情。这与现实世界的口头语言非常相似。这就像通过阅读词典并将单词强行放入英语语法中学习德语,或者学习语法并掌握单词一样。是的,德语说话者可以理解一个人从词典中背诵的单词,但每句话中的WTF数量会更高。


3

我基于这里和其他网站上的答案创建了这个帮助方法。

用法:

InvokeGenericMethodWithRuntimeGenericArguments( MyMethodWithGenericType<IType>, new[] {MyRuntimeGenericType}, null);

方法:

public static object InvokeGenericMethodWithRuntimeGenericArguments(Action methodDelegate, Type[] runtimeGenericArguments, params object[] parameters)
        {
            if (parameters == null)
            {
                parameters = new object[0];
            }
            if (runtimeGenericArguments == null)
            {
                runtimeGenericArguments = new Type[0];
            }

            var myMethod = methodDelegate.Target.GetType()
                         .GetMethods()
                         .Where(m => m.Name == methodDelegate.Method.Name)
                         .Select(m => new
                         {
                             Method = m,
                             Params = m.GetParameters(),
                             Args = m.GetGenericArguments()
                         })
                         .Where(x => x.Params.Length == parameters.Length
                                     && x.Args.Length == runtimeGenericArguments.Length
                         )
                         .Select(x => x.Method)
                         .First().MakeGenericMethod(runtimeGenericArguments);
            return myMethod.Invoke(methodDelegate.Target, parameters);
        }

1
尝试在运行时替换类型参数将会破坏类型安全的整个目的,这是由C#编译器强制执行的。C#编译器确保在编译时指定类型参数,并且在运行时不存在类型参数的歧义。我怀疑您无法在通用类型中在运行时替换类型参数。指定类型参数为“Type”的类型几乎就像具有未绑定的通用类型一样。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接