Search code examples
c#unity-game-enginecastingrealproxyas-operator

Casting and As operator resulting in the casted object and a null reference respectively


This question is almost purely for learning purposes. Environment used is the Unity 3D engine.

I've been using the RealProxy and MarhalByRefObject classes in C# to decorate one of my classes. Specifically, I created a generic proxy class using the below constructor. The class I'm decorating also has a SerializableAttribute, along with inheriting from MarshalByRefObject.

public DynamicProxy(T decorated) : base(typeof(T))
{
} 

To get the decorated object, the simplest code (without decoration) is as follows

ClassA toDecorate = new ClassA();
DynamicProxy proxy = new DynamicProxy<ClassA>(toDecorate);
Debug.Log(proxy.GetTransparentProxy() as ClassA);
Debug.Log((ClassA)proxy.GetTransparentProxy());    

This is where it got weird. I checked the type by reflection, and it was indeed the same type as the object I wanted to decorate. The confusing part, though, is that when I cast normally (ClassA), I get a reference to the decorated object, whereas when I use the as operator, a null reference is returned.

I found this behavior when I was testing my build for Unity v. 2019.1.8f1. The scripting runtime version and the API I'm using are both the .NET 4.x equivalent.

If anyone has had similar problems, I'd like to hear about them, since casting and as operator behaving differently should not happen and may result in massive loss of time and effort. I'm not really asking for a solution, but rather the opinions of people who may have a better idea than me or have encountered a similar problem.

NOTE : This behavior does not occur if I simply do

ClassA t = new ClassA();
object o = t;
Debug.Log(o as ClassA);
Debug.Log((ClassA)o);

EDIT : Upon further investigation, it's come to my attention that the as operator essentially does this

E is T ? (T)(E) : (T)null

and what's happening is that the is operator returns false.

Providing here the code with all that's needed to reproduce the problem.

public class HelloThere: MarshalByRefObject
{
    public void DoStuff()
    {
        //Do some stuff here
        Debug.Log("Doing some stuff");
    }
}
public class Proxy<T> : System.Runtime.Remoting.Proxies.RealProxy
{
    private T _decorated;

    public Proxy(T decorated): base(typeof(T))
    {
        _decorated = decorated;
    }

    public override IMessage Invoke(IMessage msg)
    {
        //Do Stuff Before Function
        //Call Function
        var methodCall = msg as IMethodCallMessage;
        var methodInfo = methodCall.MethodBase as System.Reflection.MethodInfo;
        try
        {
            var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
            return new ReturnMessage(
              result, null, 0, methodCall.LogicalCallContext, methodCall);
            //Do Stuff After function
        }
        catch (Exception e)
        {
            return new ReturnMessage(e, methodCall);
        }
    }
}

Furthermore, the code to check what each "casting" returns:

Proxy<HelloThere> proxy = new Proxy<HelloThere>(new HelloThere());
Debug.Log(proxy.GetTransparentProxy() as HelloThere);
Debug.Log((HelloThere)proxy.GetTransparentProxy());

Solution

  • The problem seems to originate from Unity 3D Engine itself and specifically it using a variant of Mono and not .NET. The version that produced this bug is Unity v. 2019.1.8f1. It was not present in Unity 2017.x versions. It's also - as expected - not present in an application developed by the standard .NET.