【问题标题】:Generic method to type casting类型转换的通用方法
【发布时间】:2011-07-22 19:57:52
【问题描述】:

我正在尝试编写转换类型的通用方法。我想写类似Cast.To<Type>(variable) 而不是(Type) variable。 我这个方法的错误版本:

public class Cast
{
    public static T To<T>(object o)
    {
        return (T) o;
    }
}

这是一个简单的测试:

public class A
{
    public static explicit operator B(A a)
    {
        return new B();
    }
}

public class B
{
}

A a = new A();
B b = Cast.To<B>(a);

如您所料,此代码将失败并显示InvalidCastException

此代码是否失败是因为虚拟机不知道如何在运行时将object 类型的变量转换为B 类型?但是异常消息说:“无法将 A 类型的对象转换为 B 类型”。所以 CLR 知道变量o 的真实类型,为什么它不能执行转换?

这里是主要问题:我应该如何重写方法T To&lt;T&gt;(object o) 来解决这个问题?

【问题讨论】:

  • 我不太喜欢“将 anything 转换为 T”的想法。我想我对 C# 的了解还不够。
  • 这是我的主要问题:为什么? Cast.To&lt;B&gt;(...)(B)... 更容易使用在什么方面?
  • @StriplingWarrior:JeffN825 解释 - 方法使流畅的界面风格调用成为可能。我自己看不出其他原因......
  • @StriplingWarrior,请不要误解这个问题,我不是要替换传统的类型转换,我想了解 CLR 的工作原理以及如何解决所描述的问题。
  • @shidzo:啊,如果只是为了更好地理解 CLR,我可以理解。

标签: c# .net generics casting types


【解决方案1】:

关于运营商解决方案的所有说法都是正确的......但这是我对您的主要问题的回答:

    public static T To<T>(this object o)
    {
        return (T)(dynamic)o;
    }

这里的关键是,将 o 转换为 dynamic 将强制 .NET 在运行时搜索显式运算符。

另外,为什么不让它成为一个扩展方法呢?

代替

        A a = new A();
        B b = Cast.To<B>(a);

你可以的

        A a = new A();
        B b = a.To<B>();

将其作为扩展方法公开的另一个好处是,您可以获得一个用于显式转换的流畅接口(如果您喜欢那种东西)。我一直讨厌在 .NET 中显式转换所需的嵌套括号平衡量。

所以你可以这样做:

a.To<B>().DoSomething().To<C>().DoSomethingElse() 

而不是

((C)((B)a).DoSomething())).DoSomethingElse()

在我看来,它看起来更清晰。

【讨论】:

  • 不!请不要在这里介绍dynamic!这不是矫枉过正,而是矫枉过正:))
  • 扩展方法是一个好主意,如果你只打算用少数几个类来做这件事,但再多的几个就不会做很多工作了。
  • 我喜欢拥有扩展方法这一事实,因为这意味着您为显式转换公开了一个流畅的接口(我一直讨厌在 .NET 中显式转换所需的嵌套括号平衡量。所以你可以这样做:a.To().DoSomething().To().DoSomethingElse() 而不是 ((C)((B)a).DoSomething())).DoSomethingElse
  • 是的,我知道方法扩展。老实说,泛型类型转换的这个问题正是在我想要更清晰的语法来进行转换时产生的,正如你上面所说的。我同意你的看法,我也不喜欢大量的括号,想写a.To&lt;B&gt;()之类的东西。但是方法扩展没有实际意义,将它们应用于object 类型并不是一个好主意。
  • 当然,过度使用应用于对象类型的扩展方法不是一个好主意……但我会说这个用于强制转换的方法非常好。
【解决方案2】:

如果您可以使用 c# 4.0,则可以:

namespace CastTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {

            A a = new A();
            B b = Cast.To<B>(a);
            b.Test();

            Console.Write("Done.");
            Console.ReadKey();
        }

        public class Cast
        {
            public static T To<T>(dynamic o)
            {
                return (T)o;
            }
        }

        public class A
        {
            public static explicit operator B(A a)
            {
                return new B();
            }
        }

        public class B
        {
            public void Test()
            {
                Console.WriteLine("It worked!");
            }
        }

    }
}

【讨论】:

  • 感谢您解决主要问题!
  • 这很有趣 - dynamic 在这里做什么?
【解决方案3】:

你可以通过反射找到正确的方法来做到这一点:

public static T To<T> (object obj)
{
    Type sourceType = obj.GetType ();
    MethodInfo op = sourceType.GetMethods ()
                    .Where (m => m.ReturnType == typeof (T))
                    .Where (m => m.Name == "op_Implicit" || m.Name == "op_Explicit")
                    .FirstOrDefault();

    return (op != null)
        ? (T) op.Invoke (null, new [] { obj })
        : (T) Convert.ChangeType (obj, typeof (T));
}

在 .NET 4.0 中,您可以按照其他答案中的建议使用 dynamic 关键字。

【讨论】:

  • 感谢反射解决方案。但它并不真正完整。我还有一个测试要给你:double d = 1.5; int i = Cast.To&lt;int&gt;(d);。这将因 InvalidCastException 而失败。
【解决方案4】:

您的 Cast.To&lt;T&gt;() 只是试图将对给定对象的引用解释为对 T 的引用。这当然失败了。

如果编译器遇到(B) a 并且知道aA 类型并且A 类型具有编译时类型转换运算符B - 它会发出这个类型转换。这不是你的情况。

【讨论】:

【解决方案5】:

Instance a 是一个 object 到转换为 B 的那一刻。不是A 类型,而是object。因此,不可能将 object 转换为 B,因为 CLR 无法知道 o 包含显式运算符。
编辑:
是的!这是解决方案:

public class Cast
{
    public static T1 To<T1>(dynamic o)
    {
        return (T1) o;
    }
}

现在 CLR 确切地知道,oA 类型的实例,并且可以调用显式运算符。

【讨论】:

  • 不正确。可以将object 转换为B。如果object 变量包含B 的实例。无法确定是否需要调用显式转换方法才能将给定的object 转换为B - 没错。
  • @Ivan,您误解了@DotNET 的观点:显式运算符是针对A 而不是object 注册的。
  • 但是,这仍然行不通。这将导致编译器错误Cannot convert type 'T2' to 'T1'。这是因为运算符重载决议发生在编译时,当 Cast 类被编译时,T1 只是 object,因此仍然无法拾取转换器。
  • @Ivan 是对的,DotNET 是对的,只是他说错了。
  • @Kirk Woll:这似乎是一场文字游戏。 “cast”是指从另一种类型的值中获取B 类型的值。并且不调用强制转换运算符。
【解决方案6】:

如果没有“类型转换器”(一个手动映射所有已知类型的属性的过程,这根本不会发生),您将永远无法使用它。您根本不能只将一个不相关的具体类转换为另一个。它会破坏单一继承模型(这是现代 OOP 的定义原则之一 - 阅读“钻石问题”)

还注意到接口(多态性) - 两个类也必须从同一个接口派生(沿着相同的路线)

【讨论】:

  • 钻石问题只是稍微涉及到这些东西。这里是关于类型系统和类型安全的。顺便说一句,这些类型中的任何一种都可以是接口。那么单继承又是什么呢? :)
  • TypeConverter 是什么意思? OP 确实 注册了一个用户类型转换运算符。如果您实际上是指System.ComponentModel.TypeConverter,那似乎是题外话,因为这是语言问题,而不是 API 问题。
  • 我认为这里提到TypeConverter是因为原则而不是因为具体实现。如果你需要从类层次结构的不同部分转换类,你真的应该有这样的类。所以TypeConverter 点对我来说似乎是正确的。
  • 我已经编辑包含短语“具体类”Ivan,是的类型转换器是指映射属性的通用术语(这意味着我们必须对所有已知和未知类型这样做这当然是不可能的)。
  • 恐怕我不明白这里描述的问题和单继承模型是如何相交的。但我想我已经理解了你对“类型转换器”的想法。在这种情况下,我同意您的看法,使用 IConvertibleTypeConverter 或自定义“类型转换器”不会是一个坏主意。
【解决方案7】:

我实际上不止一次遇到过这个问题,当我可以将自己限制为实现IConvertible 接口的类型时,它并不觉得OOP“脏”。然后解决方案实际上变得非常干净!

private T To<T>(object o) where T : IConvertible
{
    return (T)Convert.ChangeType(o, typeof(T));
}

例如,当我编写一个分词器时,我使用了它的一个变体,其中输入是一个字符串,但其中的令牌可以被解释为字符串、整数和双精度数。

由于它使用Convert 类,编译器实际上将有信息知道该做什么。这不仅仅是一个简单的演员表。

如果您需要一种更通用的转换方式,我不得不怀疑这是否不是代码中的设计问题。我认为扩大这些东西的范围的一个问题是,你试图覆盖的领域越多,外人就越难知道这种方法能做多少。

我认为最重要的是,当有人专门为该工作编写了一种方法以避免Add(x, y) 仅针对xy 的某些值的情况时,强制转换真正起作用。

我认为,如果您自己尝试铸造,预期会有所不同,例如 T1 x = (T1) T2 y。然后我认为更明显的是,您真的是靠自己,因为您只是编造了一些演员阵容,而不是所谓的“覆盖所有演员阵容方法”。

在这种情况下,很明显它专门处理实现IConvertible 的对象,开发人员可以假设它可以很好地处理任何这些对象。

也许不是每个人都同意的面向对象哲学的沉重答案,但我认为这类“概念性问题”通常会出现在编程哲学中。

【讨论】:

    【解决方案8】:

    也许不是你要做什么,但这会奏效:

    public class Cast
    {
        public static targetType To<soureType, targetType>(object o)
        {
            return (targetType)((sourceType) o);
        }
    }
    

    不过,这样的方法对我来说似乎没什么用......

    【讨论】:

    • 哇。 +1 仅用于工作示例。这似乎是这个主题的一个问题:) 但你是对的。它很慢而且没用。
    • 哇,停下来。我在这里错过了问题:) 这与第一次 DotNET 的尝试相同。编译器不知道将 sourceType 转换为 targetType 的方法。啊啊啊!您的 +1 已锁定,我无法收回。幸运的你,不专心的我))
    • 这不是一回事,您必须提供对象的真实类型(sourceType)和要转换的类型。这意味着可以将 sourceType 显式转换为目标类型。同public static T1 To&lt;T1, T2&gt; (T2 o) { return (T1) o; }
    • 以我的第一个示例为例,您可以使用A a = new A (); B b = Cast.To&lt;A, B&gt; (a);。它有效,但没用。
    • 不,不能。在编译器的编译时,这些只是两种泛型类型。想象一下 A 和 B 被放置在不同的组件中。并且编译器不知道是否存在从sourceTypetargetType 的转换,因为尚未编写用法。它告诉(从真实编译器复制和粘贴):“无法将类型'sourceType'转换为'targetType'”。期间。
    猜你喜欢
    • 2010-12-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多