C# 动态类型: Convert.ChangeType 与 Cast 的区别

9

有人能解释一下为什么将动态对象转换为类会返回该类,而使用Convert.ChangeType则返回一个动态对象,尤其是在运行时?例如:

 dynamic dObject = new SomeClass();
 var dTest1 = dObject as SomeClass;                           // returns SomeClass
 var dTest2 = Convert.ChangeType(dObject, typeof(SomeClass)); // returns dynamic

更广泛的问题:我有一系列实现通用接口的辅助类。我需要将这些类的列表传递给其他对象;但是不同的辅助类对于通用参数使用不同的类型,因此我无法直接传递辅助类的列表:
interface IHelper<T>
{
    IEnumerable<T> Foo();
}
var HelperList = new List<IHelper<T>> // Can't do this because T varies from helper to helper!

因此,我认为可以通过创建一个包含辅助类和通用类型的容器类,并利用动态性来欺骗运行时:

class ContainerClass
{
    IHelper<dynamic> HelperClass;
    Type dType;                      // Specifies the type for the dynamic object
}

现在我可以创建并传递一个ContainerClass列表。所有的处理都很顺利,直到我需要将Foo()的结果赋值回目标IEnumerable时,此时我会遇到运行时错误,说类型对象不能转换为某个具体类,即使未装箱的对象类型与所需类型匹配。如果我尝试像上面的dTest2中的类似语法,运行时仍然无法找到"转换"。我意识到这可能是对dynamic的滥用和糟糕的编程实践。如果我能找到另一种解决方案,我肯定会使用它,但现在我要么需要让它工作,要么选择更少野心的方案。
2个回答

9
在执行时,实际上并不存在所谓的“dynamic”。
但是,对于Convert.ChangeType的调用提供了一个dynamic值作为参数。任何使用dynamic参数的方法调用都被视为具有dynamic的返回值,因为编译器在执行时不知道实际签名是什么。
然而,如果你使用强制转换、is或as表达式,或者构造函数调用,那么结果只能有一个类型 - 所以这就是表达式的类型。
至于你更广泛的问题 - 我不确定使用dynamic是否特别有帮助。你可能想要为IHelper声明一个基本接口 - 一个非泛型接口,只用于实际的IHelper实例。然后你可以拥有一个List,其中每个元素实际上都是某个T的IHelper,但是T会在实例之间变化。在这里,IHelper接口并不是真正需要的,虽然如果你的IHelper接口确实包含其他不使用T的成员,那么这些成员可以移到IHelper中。然而,仅仅为了清晰起见而拥有它也是有用的。
现在,当你需要使用特定的IHelper时,动态类型可能会短暂地有用。你可以声明一个泛型方法,让动态类型在执行时确定类型参数。例如:
private readonly IList<IHelper> helpers;

...

public void UseHelpers()
{
    foreach (dynamic helper in helpers)
    {
        UseHelper(helper); // Figures out type arguments itself
    }
}

private void UseHelper<T>(IHelper<T> helper)
{
    // Now you're in a generic method, so can use T appropriately
}

谢谢Jon,这澄清了强制转换和动态类型的区别。实际上,我需要管理从IHelper<T>实例返回的IEnumerable<T>集合,这些集合稍后用于通过反射填充新类实例的属性。然而,您的建议给了我更多想法;如果遇到问题,我会尝试并发布一个带有更多细节的新问题。 - tek

4
基于Jon Skeet的答案,我认为你可以做一些有趣的事情,并避免使用动态关键字,因为它会影响性能。 使用:IHelper>。现在你可以像将helpers存储到List中一样存储它们。现在你可以通过将类型映射到一个泛型方法来调用Foo方法。
public IEnumerable<T> UseHelper<T> (IHelper<T> helper)
{

}

delegate IEnumerable<object> UseHelperDelegate(IHelper helper)
Dictionary<Type, UseHelperDelegate> helpersMap;

helpersMap.Add(typeof(int), UseHelper<int>); // Add others if you want

public IEnmerable<object> UseHelperWithMap(IHelper helper)
{
    Type helperType = helper.GetType();
    IEnumerable<object> retValue;
    if (helpersMap.Contains(helperType))
    {
         retValue = helpersMap[helperType](helper);
    }
    else // if the type is not maped use DLR
    {
         dynamic dynamicHelper = helper;
         retValue = UseHelper(dynamicHelper)
         // I wonder if this can actually be added to the map here
         // to improve performance when the same type is called again.
    }
}

注意:由于协变和逆变性,您可以将IEnumerable<SomeClass>转换为IEnumerable<object>并使用UseHelper<SomeClass>来使用UsehelperDelegate
编辑:事实证明,您实际上可以从泛型创建一个新的具体函数,并将其添加到映射中。这样,您就可以避免使用dynamic
var useHelperGeneric = this.GetType().GetMethods().FirstOrDefault(
               m=> m.IsGenericMethod && m.Name == "UseHelper");
var useHelper = useHelperGeneric.MakeGenericMethod(new Type[] { helper.GetType() });
var newFunction = (UserHelperDelegate)useHelper.MakeDelegate(typeof(UseHelperDelegate));

helpersMap.Add(helper.GetType(), newFunction);

newFunction(helper);

2
我更倾向于简单的代码而不是“聪明”的代码。这可能并不是性能瓶颈 - 动态类型已经有一些相当智能的缓存了。 - Jon Skeet

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