【问题标题】:C# Implicit Conversion Operator and is/as operatorC# 隐式转换运算符和 is/as 运算符
【发布时间】:2013-08-25 18:53:14
【问题描述】:

我正在创建一个复合对象,我希望它能够在任何可以使用其包装对象的任何地方使用。所以给定以下对象:

public class Foo{}

public class FooWrapper{
    private Foo _foo = new Foo();

    public static implicit operator Foo(FooWrapper wrapper)
    {
       return wrapper._foo;
    }
}

我想让以下测试通过:

public class ConversionTests
{
     private FooWrapper _uat = new FooWrapper();

     [Test]
     public void Can_Use_Is_Operator()
     {
          Assert.IsTrue(_uat is Foo);
     }

     [Test]
     public void Can_Use_As_Operator()
     {
          Assert.IsTrue(null != _uat as Foo);
     }
}

我查看了 MSDN 文档:
是:http://msdn.microsoft.com/en-us/library/scekt9xw.aspx
如:http://msdn.microsoft.com/en-us/library/cscsdfbt%28v=vs.110%29.aspx

文档暗示这是不可能的,

请注意,is 运算符仅考虑引用转换、装箱转换和拆箱转换。不考虑其他转化,例如用户定义的转化。

有谁知道是否有一种方法可以构造 FooWrapper 以便转换有效吗?也许实现一个像 IConvertible 这样的接口?

额外问题:有人知道为什么 as/is 不考虑用户定义的转换吗?

(对不起,如果这个问题是重复的。'as' 和 'is' 似乎是 stackoverflow 上的停用词(与大多数搜索引擎一样),因此很难搜索包含这些关键字的问题。)

【问题讨论】:

    标签: c# .net type-conversion


    【解决方案1】:

    is 文档暗示这是不可能的

    文档是正确的。

    有谁知道是否有一种方法可以构造 FooWrapper 以便转换有效吗?

    我知道。那没有。 (除了,显然,使FooWrapper 成为Foo 的派生类。)

    isas 运算符可以告诉您对象到底是什么。不要试图让他们撒谎。

    也许实现像 IConvertible 这样的接口?

    没有。

    有人知道为什么 as/is 不考虑用户定义的转换吗?

    首先,因为就像我刚才说的,is 的目的是告诉你对象真正是什么

    其次,因为假设为了论证,C# 编译器团队希望添加一个新的运算符,例如 frob,它具有使用用户定义转换的 as 运算符的语义。当xFooWrapper 时,x frob Foo 会返回Foo。请描述您希望为该表达式生成的 IL。我想通过参与这个练习,你会明白为什么这是一个难题。

    【讨论】:

    • 看完你的回复(还有@alexei-levenkov和@jim-mischel),我想我可能对isas运营商的意图有误解。我很少需要知道一个对象到底是什么,我只关心如何使用它。所以如果我有List<IBar>Foo,以及其他几个类实现IBar,当我枚举列表时,我不关心实际对象是什么,我只关心我能做什么它:foreach(var item in new List<IBar()>{ var foo = item as Foo; if (null != foo){ /*callSomeMethodThatWantsFoo(foo)*/} 这不是正确的用例吗?
    • @ppittle: 好的,那么我们假设Bar 实现了IBar,列表包含一个Bar,并且有一个用户定义的从BarFoo 的转换。您还没有回答我的问题,即:您希望为 as 运算符生成什么 IL 以允许 Foo 出现在另一端?
    • @ppittle 好的,试试你建议的代码。它做你认为它做的事吗?
    • @ppittle:你的猜想是正确的。两者都无法编译,因为在编译时没有足够的信息来生成既高效又正确的代码。根本问题是演员表意味着两个的东西:它可能意味着“我知道这个对象真的是其他类型的;如果我错了,你可以抛出异常”。它可能意味着“我知道这个对象真的不是其他类型的;编译器,请找到一种机制来获取这种类型的对象并生成目标类型的'关联'对象”。这些是对立的!
    • @ppittle:编译器为第一种类型转换生成高效代码没有问题——保留表示转换。这种转换只是对对象类型标签的动态检查,如果检查失败则抛出异常。但是编译器无法为第二种类型转换生成有效的代码——表示改变转换——除非源和目标类型对编译器已知
    【解决方案2】:

    为了将FooWrapper 视为FooFooWrapper 必须继承自Foo。那就是:

    class FooWrapper: Foo
    {
    }
    

    但问题是,如果您希望包装器也包装Bar 对象,则不能。因为 C# 不支持多重继承。

    通常,如果您需要一个像多个对象一样工作的包装器,您可以让它实现接口。因此,例如,您将拥有:

    public interface IFoo
    {
    }
    
    public class Foo: IFoo  // implements the IFoo interface
    {
    }
    
    public interface IBar
    {
    }
    
    public class Bar: IBar
    {
    }
    
    public class MyWrapper: IFoo, IBar  // Implements the IFoo and IBar interfaces
    {
        private IFoo _theFoo;
        private IBar _theBar;
        public MyWrapper(IFoo foo, IBar bar)
        {
            _theFoo = foo;
            _theBar = bar;
        }
    }
    

    当然MyWrapper 必须实现IFooIBar 的所有方法,将调用传递给正确的包含对象。因此,如果IFoo 声明了一个Frob 方法,您将拥有(在MyWrapper 类中):

        public void Frob()
        {
            _theFoo.Frob();
        }
    

    这是隐式接口实现。您可能还需要研究接口方法的显式实现。

    创建包装器:

    MyWrapper wrapper = new MyWrapper(new Foo(), new Bar());
    

    那么,您的测试将使用isas 来检查接口而不是具体的类。

    var isFoo = wrapper is IFoo;
    IFoo myFoo = wrapper as IFoo;
    

    【讨论】:

    • “但是,问题在于,如果您希望包装器也包装 Bar 对象” - 用那个一针见血。我正在尝试堆肥多个对象(FooBar 等),所以直接继承不存在了。我正在检查框架对象,所以我不能通过添加额外的接口来修改包装的对象。
    【解决方案3】:

    is/as 与包装类一起工作的唯一选择是包装类确实是包装类。如果您要包装的类不是密封的,您可以从中派生...

    现在,在一般情况下,您尝试实际实现的目标是不可能的 - .Net/C# 静态检测将调用哪些方法,并且必须直接在该类型的对象上调用包装对象的所有非虚拟方法(或派生的),但您将无法以任何方式覆盖它们。静态方法更难。

    var item = new InnerType();
    // you can't create any class that will replace method in this call
    item.NonVirtual();
    
    // No way to replace static method with wrapper
    var result = InnerType.StaticMethod();
    
    // At least virtual methods can be overriden if Wrapper derives from InnerType
    item.VirtualMethod();
    

    如果您的代码使用接口与对象交互,您的任务会容易得多,因为完全支持用替代实现替换接口。手动包装器或通过反射/发射自动创建都可以。

    var itemByInterface = (IInnerType)Factory.CreateInnerType();
    itemByInterface.InterfaceMethod();
    
    // is/as checks should use interface
    var isRightType = itemByInterface is IInnerType;
    
    // do not use static/non-interface calls 
    

    我为什么不考虑其他转换的想法:

    • 预期操作是非常快速和轻量级的检查 - 其他转换可能需要创建对象/无法预测需要多长时间,
    • 它显着增加了编译器可以考虑的选项数量,从而减慢了编译速度
    • 在许多其他使用对象的情况下,不会考虑转换,因此可能会导致代码行为不一致,is 报告成功但对象未能正确使用(例如添加到列表中的实例)。

    【讨论】:

      猜你喜欢
      • 2010-10-27
      • 2011-11-03
      • 1970-01-01
      • 2016-06-01
      • 2010-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-18
      相关资源
      最近更新 更多