【问题标题】:Casting between two types derived from the (same) interface从(相同)接口派生的两种类型之间的转换
【发布时间】:2012-01-13 22:26:47
【问题描述】:

我有一个接口和两种派生自它的类型。

但是,我不能执行以下操作:

B objectB = (B) objectA

其中 B 派生自 Interface1(我正在编造类的名称,但重点仍然存在),同样适用于 objectA(属于 A 类型)。我收到以下错误消息:

无法将 A 类型的表达式转换为 B。

这两种类型都是从接口派生的,我缺少什么?

【问题讨论】:

    标签: c# .net


    【解决方案1】:
    1. 类型不派生自接口。他们实现一个界面。
    2. 大象和蜘蛛都是动物这一事实并不意味着 您可以将一种转换为另一种。

    【讨论】:

    • 我真的很喜欢这个简洁的答案 - 简单、清晰、干净地获得所有必要的信息
    • 后者是特别重要的一点;蜘蛛猪已经够危险了。
    • 解释为什么默认情况下不可能并不能算作完整答案。您可以提到一个解决方案,例如创建 explicit operator
    • 但我想将OfficeWorker 转换为Boss 并希望传输他们实现的IEmployee 接口提供的所有属性。为什么 C# 没有为这种情况提供任何东西?就像 JSON-way-workaround 已经做的那样:Boss bossObj = JsonSerializer.Deserialize<Boss>(JsonSerializer.Serialize(officeWorkerObj)),尽管在这种情况下它们既不必共享接口也不必从同一个类派生。
    • @MartinSchneider 我明白你的意思。但在一般情况下,这看起来并不容易。想象一下“IPrintable”界面。也许 Grid 是可打印的,而 Graph 也是可打印的;他们都实现了接口。但这并不意味着您可以将表格转换为图表。当然,您可以为其中一个类编写一个构造函数,将另一个类作为参数。在您的示例中,您可能需要在构造函数中初始化 Boss 的其他属性,这些属性不包含在 IEmployee 接口中,也不是 OfficeWorker 的属性。
    【解决方案2】:

    一个对象可以分配给一个祖先(直接或间接基类型)或它实现的接口,但不能分配给兄弟姐妹(即从共同祖先派生的另一种类型);但是,您可以声明自己的显式转换:

    class FooObject : IObject
    {
        public string Name { get; set; }
        public int Value { get; set; }
    
        public static explicit operator FooObject(BarObject bar)
        {
            return new FooObject { Name = bar.Name, Value = bar.Value };
        }
    }
    
    class BarObject : IObject
    {
        public string Name { get; set; }
        public int Value { get; set; }
    
        public static explicit operator BarObject(FooObject bar)
        {
            return new BarObject { Name = bar.Name, Value = bar.Value };
        }
    }
    

    现在你可以写了

    var foo = new FooObject();
    var bar = (BarObject)foo;
    

    var bar = new BarObject();
    var foo = (FooObject)bar;
    

    不会出错。

    如果感觉自然,您还可以创建implicit 转换。例如。 int 可隐式转换为 double: int i = 5; double x = i;

    (这也是对封闭问题How do I cast Class FooObject to class BarObject which both implement interface IObject?的回答)。

    【讨论】:

      【解决方案3】:

      如果它们共享的只是一个公共接口,则您不能从A 转换或转换为B,除非您实际定义自己的转换运算符,假设您控制其中一种类型的源,或者使用另一个提供的用户-由确实控制源的人提供的定义转换。 (但是,这种用户定义的转换不会保留原始对象。一个对象进入转换,另一个对象出来。)

      您可以将A 转换为Interface1,将B 转换为Interface1。但是两种类型只是共享一个共同的父对象并不能使这两种类型相互转换

      A a = new A(); 
      B b = new B();
      Interface1 obj1 = a; // legal
      Interface1 obj2 = b; // legal
      B obj3 = (B)a; // not legal, a is simply not a B
      

      tobias86 在下面的评论中说得很好,你有一只猫和一只狗。两者都派生自Animal。但猫不是狗。


      作为扩展,您可能会为如何以及为何使用界面而苦恼。您不使用接口来代替 A 代替 B,或使用 B 代替 A。您使用它来代替 A 或 B 代替 Interface1。这是您期望的接口,以及您可能提供的 A 或 B。给定:

      public void DoSomething(Interface1 obj) { } // expects 
      DoSomething(new A()); // you can supply A
      

      或者

      public Interface1 GetSomething() // callers expect to get 
      {
          return new B(); // you can supply a B
      }
      

      这是您正在编程的接口,A 和 B 仅仅是实现。您可能认为可以将 B 传递给期望 A 的事物。期望可能需要更改。

      【讨论】:

      • 不公平......不仅他的名字和我的名字相似,而且他的想法也很相似(见我的回答)>。
      • 不错的答案,但是 OP 正在谈论在他的情况下不起作用的铸造。但是转换可以工作(语法相同,语义不同)
      • @RuneFS,请参阅 this article 了解我如何使用它。在这种情况下,这是一种保留表示的转换。当它们共享的只是一个公共接口时,OP 只是试图从一种类型转换或转换为另一种类型。这里的大多数答案只是指出了这个错误。没有人能够展示如何使该转换合法,这当然可以通过用户定义的转换运算符来完成,但那些会改变表示形式,对象不会相同。
      【解决方案4】:

      这两种类型都实现相同的接口(或具有相同的基类型,就此而言)这一事实并不能使它们可互换; A 始终是 AB 始终是 B。在继承链中,对象可以转换为自身或任何父类型。你有:

      A : ISomeInterface
      B : ISomeInterface
      

      这让您可以将A 转换为AISomeInterface,并将B 转换为BISomeInterface

      或(取决于您对“源自”的含义)

      SomeBaseType
       > A
       > B
      

      这让您可以将A 转换为ASomeBaseType,并将B 转换为BSomeBaseType

      (加上object,在每种情况下)

      【讨论】:

        【解决方案5】:

        当从 A 转换为 B 时,B 必须是 A 的超类型或对象的运行时类型必须是 B

        如果你有

        class A : B{}
        

        您可以将编译时类型 A 的对象强制转换为 B。如果对象的运行时类型为 A,您也可以将 B 类型强制转换为 A

        在您的情况下,这两种类型不共享超子类型关系。它们只共享一个共同的超类型,但这还不够。

        作为一个例子,说明为什么这不能(通常)工作,您如何将编译器从Point[] 转换为Dictionary<string,HashSet<byte>>? (都实现了 IEnumerable)

        【讨论】:

        • 如您所见,如果BA 的超类型,则从A 到B 的强制转换是合法的并且保证成功;这是合法的,但如果B 是一个子类型,它可能会在运行时失败。不过,有趣的一点是,在没有可识别子类型/超类型排列的泛型类型之间禁止转换,即使这种转换可能成功。给定泛型类型 T:Control,不能将 T 的实例转换为 Button,也不能将 Button 的实例转换为 T,尽管可以将其中一个实例转换为 Control,然后将该转换的结果转换为另一种类型。跨度>
        • supercat 相同的规则适用于一般情况。 Button 和 T 之间没有超子类型关系。将 T 实例化为 Control 是无关紧要的。但是,对 T 到 Control 的约束将改变这一点
        • @RuneFS:我想 supercat 是在谈论通用约束。但即便如此,您也无法确保现在和永远不会使用 T2:Button 以外的其他类型参数创建/调用该类型/方法的实例,因此即使那样它也会产生编译器错误。
        • @Tobias: 如果x 是 T 类型的变量,从 Control 派生,它恰好持有 DerivedButton 类型的实例(暗示 TDerivedButton 或超类型其中)。如果TControl,则将x 转换为按钮是向上转换,可能成功也可能不成功;如果TDerivedButton,那么将x 转换为按钮是一个向下转换,它总是会成功。我发现有趣的是,编译器不能将从T 转换为Button 的转换视为可能成功也可能不成功,如果失败应该抛出异常。
        • 这可能是真的,但是如果我用@987654339 调用它(来自一个外部库,当我们正在谈论的那个特定的假设库被构建时,它不存在)会发生什么@ = Window?泛型被设计为类型安全在编译时,如果允许的话,情况就不是这样了。
        【解决方案6】:

        你想做的事情没有意义。 objectA 不是 B

        【讨论】:

          【解决方案7】:

          您只能将它们转换为接口类型。 A 不是 B,但它们都是 I。这意味着您可以将 A 转换为 I 或 B 转换为 I,但不能将 B 转换为 A

          【讨论】:

            【解决方案8】:

            您需要转换为接口。

            interface IBase { }
            class A : IBase { }
            class B : IBase { }
            

            有了这个,这两种类型唯一的共同点就是接口成员。 B 可能有 A 没有的项目。

            A a = new A();
            B b = new B();
            
            IBase aBase = a;
            IBase bBase = b;
            

            然后您可以在IBase 接口上调用任何东西。

            【讨论】:

              【解决方案9】:

              想象以下设置:

              public interface Human
              {
                  bool Male { get; }
              }
              
              public class Man : Human
              {
                  public bool HasABeard { get { return true; } }
              
                  public bool IsMale { get { return true; } }
              }
              
              public class Woman : Human
              {
                  public bool IsMale { get { return false; } }
              
                  public List<Pair<Shoe>> Shoes { get; set; }
              }
              

              您希望编译器从以下代码中生成什么?输出会是什么?

              Man a;
              Woman b = new Woman();
              a = (Man)b;
              
              Console.WriteLine(a.HasABeard ? "Beard ON" : "Beard OFF");
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2012-02-25
                • 1970-01-01
                • 2017-05-04
                • 2011-02-08
                • 1970-01-01
                • 2017-05-23
                • 1970-01-01
                相关资源
                最近更新 更多