Reputation: 12295
I'm creating a composite object that I want to be able to be used anywhere one of its wrapped objects could be used. So given the following objects:
public class Foo{}
public class FooWrapper{
private Foo _foo = new Foo();
public static implicit operator Foo(FooWrapper wrapper)
{
return wrapper._foo;
}
}
I want to make the following tests pass:
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);
}
}
I've taken a look at the MSDN documentation:
is: http://msdn.microsoft.com/en-us/library/scekt9xw.aspx
as: http://msdn.microsoft.com/en-us/library/cscsdfbt%28v=vs.110%29.aspx
The is documentation implies it is not possible,
Note that the is operator only considers reference conversions, boxing conversions, and unboxing conversions. Other conversions, such as user-defined conversions, are not considered.
Does anyone know if there's a way to construct FooWrapper so is/as conversions will work? Implementing an interface like IConvertible perhaps?
Bonus question: Anyone know why as/is doesn't consider user-defined conversions?
(And sorry if this question is a dup. 'as' and 'is' appear to be stop words here on stackoverflow (as with most search engines), making it quite difficult to search for questions that contain these keywords.)
Upvotes: 8
Views: 2467
Reputation: 100527
The only option for is
/as
to work with the wrapper class is if wrapping class is indeed wrapped class. If class you want to wrap is not sealed you can derive from it...
Now what you are trying to actually achieve is not possible in general case - .Net/C# statically detects what methods will be called and all non-virtual methods of your wrapped object must be called directly on object of that type (or derived one), but you'll not be able to override them in any way. Static methods are even harder.
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();
If your code uses interfaces to interact with objects your task is much easier as substituting an interface with alternative implementation is completely supported. Either manual wrapper or automatically created via reflection/Emit would work.
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
My thoughts on why is/as do not consider other conversion:
is
reports success but object fails to be used correctly (like instance added to list).Upvotes: 1
Reputation: 659994
The is documentation implies it is not possible
The documentation is correct.
Does anyone know if there's a way to construct FooWrapper so is/as conversions will work?
I know. There is not. (Aside from, obviously, making FooWrapper
a derived class of Foo
.)
The is
and as
operators are there to tell you what an object really is. Don't try to make them lie.
Implementing an interface like IConvertible perhaps?
Nope.
Anyone know why as/is doesn't consider user-defined conversions?
First, because like I just said, the purpose of is
is to tell you what the object really is.
Second, because suppose for the sake of argument the C# compiler team wished to add a new operator, say frob
, that has the semantics of an as
operator that uses user-defined conversions. x frob Foo
gives you back a Foo
when x
is a FooWrapper
. Describe please the IL you would like to see generated for that expression. I think by engaging in this exercise you'll see why it is a hard problem.
Upvotes: 11
Reputation: 133975
In order for FooWrapper
to be considered a Foo
, FooWrapper
would have to inherit from Foo
. That is:
class FooWrapper: Foo
{
}
The problem, though, is that if you wanted your wrapper to also wrap Bar
objects, you can't. Because C# does not support multiple inheritance.
Typically if you need a wrapper that will act like multiple objects, you have it implement interfaces. So, for example, you would have:
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;
}
}
And of course MyWrapper
would have to implement all the methods of IFoo
and IBar
, passing the calls on to the proper contained object. So if IFoo
declared a Frob
method, you would have (in the MyWrapper
class):
public void Frob()
{
_theFoo.Frob();
}
That's implicit interface implementation. You might also have to look into explicit implementation of interface methods.
To create a wrapper:
MyWrapper wrapper = new MyWrapper(new Foo(), new Bar());
Your tests, then, would use is
and as
to check for interfaces rather than concrete classes.
var isFoo = wrapper is IFoo;
IFoo myFoo = wrapper as IFoo;
Upvotes: 2