-1

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());
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
G.Pap
  • 7
  • 4
  • 2
    Its a little unclear what you are asking, where is the other implementation that is not working? – TheGeneral Jul 05 '19 at 03:38
  • 1
    Maybe an explicit conversion exists between whatever type `GetTransparentProxy` actually returns and `ClassA`? – Sweeper Jul 05 '19 at 05:44
  • @TheGeneral You can indeed argue I've left out stuff that I thought weren't needed. I've updated the post. As stated though, it's more of a "have you encountered this before" or "have you any idea if this should happen" rather than what I should do to fix this. – G.Pap Jul 05 '19 at 15:10
  • It would be really helpful if you could provide a [mcve] so that we could investigate it. – Jon Skeet Jul 05 '19 at 16:22
  • @Sweeper GetTransparentProxy returns an object type of reference type object but reflection type the type you provided it with. I've also found something weird with the is operator, which I edited in the post. The idea about some weird explicit casting might be good. The truly weird thing is that I've used this before with the as operator and it worked correctly back then. – G.Pap Jul 05 '19 at 16:31
  • @JonSkeet I've provided the example. What I'm afraid of is if the problem is Unity specific. – G.Pap Jul 05 '19 at 16:59
  • 1
    _"casting and as operator behaving differently should not happen"_ -- that assumption is clearly false, because while the `as` operator can't be overloaded, casting can be, through the declaration of an explicit conversion operator. I think it's almost certain, [as suggested by @Sweeper](https://stackoverflow.com/questions/56896119/casting-and-as-operator-resulting-in-the-casted-object-and-a-null-reference-resp#comment100339970_56896119), that that's what's going on here. Such an overload would have to be defined in `ClassA`. – Peter Duniho Jul 05 '19 at 18:49
  • Unfortunately, while you say you're using "RealProxy", the type names actually shown in your post are `DynamicProxy` and `Proxy`, neither of which are actually the `System.Runtime.Remoting.Proxies.RealProxy` class that one might assume from your statement about using "RealProxy". So your question is not clear at all. – Peter Duniho Jul 05 '19 at 18:53
  • @PeterDuniho Proxy does inherit from RealProxy and is a stripped down version of DynamicProxy. I pasted its code in the pastebin. Secondly, I may be getting this wrong, but even if you do overload the casting, the as operator does cast, assuming the is result is true, as I've posted in my question – G.Pap Jul 05 '19 at 19:04
  • _"I pasted its code in the pastebin"_ -- nope. 100% of the relevant code belongs in your question itself. You may use external links to provide optional details, but everything to fully comprehend the question is _required_ to be contained within the question itself. – Peter Duniho Jul 05 '19 at 19:07
  • _"even if you do overload the casting, the as operator does cast, assuming the is result is true"_ -- you are incorrect. Indeed, conversions are explicitly excluded. If you try to use `as` on unrelated types, you get _"error CS0039: Cannot convert type '(some type)' to '(some other type)' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion"_ – Peter Duniho Jul 05 '19 at 19:13
  • I get non-null results for both `as` and the explicit cast, using the code you posted. – Peter Duniho Jul 05 '19 at 19:19
  • Thank you for updating your post. Still, please note my most recent comment before this one. I'm unable to reproduce your claim, using the code you posted. – Peter Duniho Jul 05 '19 at 19:25
  • @PeterDuniho Indeed, I was incorrect. User defined conversions won't work with the as operator. I did check the type that GetTransparentProxy() returns though, and it's the type of the original that it was given. Should the is operator still give false when comparing an object for which GetType() returns (lets say) A, when used to compare it against type A? Were you using Unity? I had it working in console apps / other Unity versions. This question was also made to check if others had encountered the same problem, not necessarily solve this one. – G.Pap Jul 05 '19 at 19:26
  • _"I did check the type that GetTransparentProxy() returns though"_ -- careful. If you call `GetType()` you'll indeed get the proxied type. But it's really not...it's a proxy type. The CLR does "magic" to make it look like your original proxied type (hence "transparent"). In the context of the .NET proper, `is`, `as`, casting, and `GetType()` will make the object look like the original proxied type, per the whole point of the proxy. – Peter Duniho Jul 05 '19 at 19:33
  • _"Were you using Unity?"_ -- no, and that could be pertinent. Last I recall, Unity3d does not actually use .NET, but instead some variant of Mono. I know that since their 2017 release, there has been a lot of work to try to bring parity with actual .NET environments, but there may still be gaps (such as with the proxying/remoting scenarios). See https://learn.microsoft.com/en-us/visualstudio/cross-platform/unity-scripting-upgrade?view=vs-2019 for details. – Peter Duniho Jul 05 '19 at 19:33
  • _"This question was also made to check if others had encountered the same problem, not necessarily solve this one"_ -- The Q&A format of Stack Overflow is meant for solving problems. Commiserating or seeking out shared experiences should be done elsewhere, as such types of "questions" will generally be poorly received on SO. See https://stackoverflow.com/help/on-topic for more info. – Peter Duniho Jul 05 '19 at 19:35
  • @PeterDuniho Thank you for your input! I'll update the post with an answer when I'm sure that it's entirely Unity's fault. I'll keep in mind the Q&A format. – G.Pap Jul 05 '19 at 19:37

1 Answers1

0

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.

G.Pap
  • 7
  • 4