C#: 动态运行时类型转换

91

我想要实现一个具有以下签名的方法

dynamic Cast(object obj, Type castTo);

有人知道如何做到这一点吗? obj肯定实现了castTo,但需要适当地转换才能使我的应用程序的运行时绑定工作正常。

编辑:如果某些答案不合理,那是因为我最初意外输入了dynamic Cast(dynamic obj,Type castTo); - 我的意思是输入应该是object或其他保证的基类。


你是说你需要动态调用一个隐式或显式转换运算符吗? - Gabe
只有在当前状态下,明确指定。 - George Mauer
2
相关(我在寻找C#版本的dynamic_cast,谷歌把我带到了这里,但答案在另一个线程中):https://dev59.com/YGox5IYBdhLWcg3wQSSp - Jason C
9个回答

134

我认为您在混淆强制类型转换和类型转换的问题。

  • 强制类型转换:更改指向对象的引用类型。可以向上或向下移动对象层次结构或实现接口
  • 类型转换:从原始源对象创建一个新对象,并通过对该类型的引用访问它。

在C#中,很难区分这两个概念,因为它们都使用相同的C#运算符:强制类型转换操作符。

在这种情况下,您几乎肯定不是在寻找一个强制类型转换操作。将dynamic强制转换为另一个dynamic本质上是一种恒等转换。它没有提供任何价值,因为您只是获得一个指向相同基础对象的dynamic引用。结果查找没有任何区别。

相反,在这种情况下,您似乎想要进行类型转换。即将基础对象变形为不同类型,并以dynamic方式访问生成的对象。最好的API是Convert.ChangeType

public static dynamic Convert(dynamic source, Type dest) {
  return Convert.ChangeType(source, dest);
}

编辑

更新后的问题有以下这行代码:

obj肯定实现了castTo方法

如果是这样的话,那么Cast方法就不需要存在了。源object可以直接分配给一个dynamic引用。

dynamic d = source;

看起来你想要实现的是通过动态引用查看source层次结构中的特定接口或类型。然而,这是不可能的。结果得到的dynamic引用将直接查看实现对象,而不会浏览source层次结构中的任何特定类型。因此,在层次结构中转换为不同类型,然后再返回dynamic的想法与一开始直接赋值给dynamic完全相同。它仍然指向相同的基础对象。


嗨Jared,我正在寻找转换类型,但你说得很对,我打错了问题,应该是从对象(或其他基类)到动态。现在会进行更正。 - George Mauer
1
@George 如果是这样,为什么不直接转换为 dynamic 呢?如果底层操作确实是一个转换,那就不需要这个方法。 - JaredPar
使用案例是传入BaseType对象,我有一个IList<dynamic>处理程序,其中每个处理程序实现IHandle<T>,其中T:BaseType。我需要运行适用于此特定类型的所有处理程序,因为dynamic无法正确猜测(可能是因为IHandle是协变的)。我使用反射使其工作,但是哎呀。 - George Mauer
1
在C#中,没有像改变引用类型的类型那样的“强制转换”这种东西。在C#中,转换和强制转换是相同的。顺便说一下,当您将一个对象向上转换到其继承树时,它的类型不会改变或需要改变,因为它更高类型的对象。将“int”转换为“double”会创建一个等于原始“int”的新“double”,尽管将“HttpWerbRequest”转换为“WebRequest”不会做任何事情,因为“HttpWebRequest”是“WebRequest”。至于引用类型与值类型部分... - AK_
3
代码不错,但为什么要将方法称为Convert?这容易让人陷入System命名空间的陷阱中。建议将其改为DynamicConvert或类似的名称。 - phil soady

47

这应该可以正常工作:

public static dynamic Cast(dynamic obj, Type castTo)
{
    return Convert.ChangeType(obj, castTo);
}

编辑

我编写了下面的测试代码:

var x = "123";
var y = Cast(x, typeof(int));
var z = y + 7;
var w = Cast(z, typeof(string)); // w == "130"

它确实类似于像PHP、JavaScript或Python这样的语言中找到的"类型转换"(因为它也会将值转换为所需的类型)。我不知道这是一件好事,但它肯定有效... :-)


嗯,我即将尝试,但是这样做不会一直将其向下转换为对象吗? - George Mauer
@Keith:我刚测试了一下,它确实可以使用String(这是一个类,而不是值类型)。 - rsenna
7
只是因为 String 实现了 IConvertible 接口。你使用的类型必须使用 IConvertible 方法生成,因为该方法只会进行相应的 Convert.ToABCD() 调用。因此,它仍然限制了你在引用类型之间进行转换的能力。String 刚好是一个例外,因为 IConvertible 要求你实现 ToString 方法。 - KeithS
3
抱歉,这对实体类型无效(这是我的应用场景)。 - George Mauer
2
@George Mauer:所以你的用例对我来说没有意义。正如@JaredPar的回答所解释的那样,一个对象就是一个对象——如果您已经使用动态引用它,应该能够轻松访问其方法和/或属性。所以也许问题就是你的用例……顺便说一句,这就是为什么我(错误地)认为你只是想获得简单类型值的快速转换方法的原因。 - rsenna

13

我知道这个问题已经有答案了,但是我使用了不同的方法,认为值得分享。另外,我觉得我的方法可能会产生一些不必要的开销。但是,在我们观察到的负载下,我无法观察或计算到任何糟糕的情况发生。我正在寻找对这种方法的任何有用反馈。

处理动态对象的问题在于您无法直接将任何函数附加到动态对象。您必须使用某些东西来确定您不想每次都弄清楚的分配。

在规划这个简单的解决方案时,我看了看当试图重新输入类似对象时的有效中介物是什么。我发现二进制数组、字符串(xml、json)或硬编码转换(IConvertable)是通常的方法。我不想涉及二进制转换,因为它会影响代码可维护性和懒惰。

我的理论是,Newtonsoft 可以通过使用字符串媒介来实现这一点。

作为一个缺点,我相当确定在将字符串转换为对象时,它会通过搜索当前程序集中具有匹配属性的对象来使用反射,创建类型,然后实例化属性,这将需要更多的反射。如果是真的,所有这些都可以被认为是可避免的开销。

C#:

//This lives in a helper class
public static ConvertDynamic<T>(dynamic data)
{
     return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(Newtonsoft.Json.JsonConvert.SerializeObject(data));
}

//Same helper, but in an extension class (public static class),
//but could be in a base class also.
public static ToModelList<T>(this List<dynamic> list)
{
    List<T> retList = new List<T>();
    foreach(dynamic d in list)
    {
        retList.Add(ConvertDynamic<T>(d));
    }
}

话虽如此,这也适用于我组合的另一个实用程序,它使我可以将任何对象变为动态对象。我知道我必须使用反射来正确地完成这项工作:

public static dynamic ToDynamic(this object value)
{
    IDictionary<string, object> expando = new ExpandoObject();

    foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(value.GetType()))
        expando.Add(property.Name, property.GetValue(value));

    return expando as ExpandoObject;
}

我必须提供那个函数。分配给动态类型变量的任意对象无法转换为IDictionary,并且将破坏ConvertDynamic函数。为了使用这个函数链,它必须提供System.Dynamic.ExpandoObject或IDictionary<string,object>类型的动态对象。


1
既然我已经到了未来,我知道动态是专门设计用来保存从Python脚本解析出的函数的。所以,它们可以保存函数,但这并不是最好的选择。即使第二个代码示例能够工作,我也不会再使用它了。它会增加额外的开销。 - JRodd

10

到目前为止,我得到的最好结果:

dynamic DynamicCast(object entity, Type to)
{
    var openCast = this.GetType().GetMethod("Cast", BindingFlags.Static | BindingFlags.NonPublic);
    var closeCast = openCast.MakeGenericMethod(to);
    return closeCast.Invoke(entity, new[] { entity });
}
static T Cast<T>(object entity) where T : class
{
    return entity as T;
}

在你的初始评论之后,这基本上就是我最终得出的结果。 - KeithS
3
对我来说这没有任何意义。除了在情况obj(其编译时类型为object)不是Foo的情况下,您会得到一个空引用外,调用DynamicCast(obj, typeof(Foo))(dynamic)obj有什么区别? 此外,约束where T:object毫无意义且不允许。 - Jeppe Stig Nielsen
1
@JeppeStigNielsen 这是一个关于装箱的问题。假设你有一个 Person 类,其中包含一个 GetFullName 方法,以及一个 Professor : Person 类,它重写了 GetFullName 方法并在后面添加了 "PhD" 后缀。现在假设该实体的类型为 Professor,但在我们的方法之外被强制转换为 Person,可能是由 ORM 或模型绑定器等加载的。如果你调用 ((dynamic)entity).GetFullName(),它将运行 Person.GetFullName()。这个问题已经很久没有出现过了,所以具体细节可能有点不对,但与此有关。 - George Mauer
2
还是不太明白。首先,你所写的“装箱”与值类型(如结构体)放入引用类型变量有关。你的ProfessorPerson示例必然涉及类,而不是结构体。其次,如果Professor覆盖该方法,虚拟调度将确保使用“最派生”的实现。即使发生了方法隐藏,上面的DynamicCast方法也没有用处。在所有情况下,DynamicCast(obj, typeof(Foo))等同于(obj is Foo) ? (dynamic)obj : null。只需使用(dynamic)obj即可。 - Jeppe Stig Nielsen
4
x是派生类的实例时,您似乎相信(dynamic)(BaseClass)x(dynamic)(DerivedClass)x的行为可能不同。但是这种差异应该从何处来呢?对象不会记住曾经引用它的引用类型。 - Jeppe Stig Nielsen
@GeorgeMauer 在你之前提到的情况中,你的子类是否可能没有覆盖底层方法,而只是隐藏了它?在这种情况下,存在两个具有相同名称的方法,因此引用类型将确定调用哪个方法。 - cwharris

7
开源框架Dynamitey具有静态方法,可使用DLR进行后期绑定,包括其他转换中的类型转换。
dynamic Cast(object obj, Type castTo){
    return Dynamic.InvokeConvert(obj, castTo, explict:true);
}

与使用反射调用Cast<T>相比,这种方法的优点在于它也适用于任何具有动态转换运算符的IDynamicMetaObjectProvider,例如DynamicObject上的TryConvert

3

尝试使用通用方法:

public static T CastTo<T>(this dynamic obj, bool safeCast) where T:class
{
   try
   {
      return (T)obj;
   }
   catch
   {
      if(safeCast) return null;
      else throw;
   }
}

这是扩展方法格式,因此使用方式就像它是动态对象的成员一样:

dynamic myDynamic = new Something();
var typedObject = myDynamic.CastTo<Something>(false);

编辑:糟糕,没有看到那个。是的,您可以反射性地关闭泛型,并且在非泛型扩展方法中隐藏它并不难:

public static dynamic DynamicCastTo(this dynamic obj, Type castTo, bool safeCast)
{
   MethodInfo castMethod = this.GetType().GetMethod("CastTo").MakeGenericMethod(castTo);
   return castMethod.Invoke(null, new object[] { obj, safeCast });
}

我不确定你从中能得到什么。基本上,你将一个动态对象强制转换为反射类型,然后再将其放回动态对象。也许你是对的,我不应该问。但是,这可能会实现你想要的功能。基本上,当你进入动态领域时,你失去了大多数强制转换操作的必要性,因为你可以通过反射方法或试错来发现对象的属性和行为,所以没有太多优雅的方法来做到这一点。


2
如果在编译时不知道T的类型,则此方法(无需反射)无法正常工作。 - jason
@KiethS,在运行时提供类型。我知道,别问我为什么。我想我可以编写代码并使用反射来关闭泛型,但一定有更好的方法。 - George Mauer
1
@George:我不相信你能够避免使用反射。 - Adam Robinson

2
您可以使用表达式管道来实现这一点:
 public static Func<object, object> Caster(Type type)
 {
    var inputObject = Expression.Parameter(typeof(object));
    return Expression.Lambda<Func<object,object>>(Expression.Convert(inputObject, type), inputPara).Compile();
 }

你可以像这样调用:

object objAsDesiredType = Caster(desiredType)(obj);

缺点:这个lambda的编译速度比之前提到的几乎所有其他方法都要慢。
优点:您可以缓存lambda,那么这应该是实际上最快的方法,它在编译时与手写代码相同。

2

在 @JRodd 的版本上稍作修改,以支持来自Json(JObject)的对象。

public static dynamic ToDynamic(this object value)
    {
        IDictionary<string, object> expando = new ExpandoObject();

        //Get the type of object 
        Type t = value.GetType();

        //If is Dynamic Expando object
        if (t.Equals(typeof(ExpandoObject)))
        {
            foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(value.GetType()))
                expando.Add(property.Name, property.GetValue(value));
        }
        //If coming from Json object
        else if (t.Equals(typeof(JObject)))
        {
            foreach (JProperty property in (JToken)value)
                expando.Add(property.Name, property.Value);
        }
        else //Try converting a regular object
        {
            string str = JsonConvert.SerializeObject(value);
            ExpandoObject obj = JsonConvert.DeserializeObject<ExpandoObject>(str);

            return obj;
        }

       

        return expando as ExpandoObject;
    }

-4

另外:

public static T Cast<T>(this dynamic obj) where T:class
{
   return obj as T;
}

5
要求似乎是在运行时指定类型。 - Jimmy
扩展方法的第一个参数不能是'dynamic'类型。 - Amin Mohamed
他使用它来表示任何类型都可以存在。如果您不是非常字面理解,这并不是坏事,这是一个伪例子。 - JRodd

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