4

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

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 上似乎是停用词(与大多数搜索引擎一样),因此很难搜索包含这些关键字的问题。)

4

3 回答 3

8

是文档意味着这是不可能的

文档是正确的。

有谁知道是否有一种方法可以构建 FooWrapper 以便/作为转换工作?

我知道。那没有。(显然,除了创建一个 .FooWrapper的派生类Foo。)

和运算isas用于告诉您对象的真正含义。不要试图让他们撒谎。

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

没有。

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

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

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

于 2013-08-22T21:14:47.547 回答
2

为了FooWrapper被认为是 a 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,必须实现 and 的所有方法IFooIBar将调用传递给正确的包含对象。因此,如果IFoo声明了一个Frob方法,您将(在MyWrapper类中):

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

那是隐式接口实现。您可能还必须研究接口方法的显式实现。

要创建包装器:

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

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

var isFoo = wrapper is IFoo;
IFoo myFoo = wrapper as IFoo;
于 2013-08-22T21:13:19.760 回答
1

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报告成功但对象未能正确使用的不一致代码行为(例如添加到列表中的实例)。
于 2013-08-22T21:12:04.410 回答