1

TL;DR: 1) A non-generic version of my code allows me to cast an object to an interface that I know it implements; a generic version, where I'm casting to a generic type, will not compile. Am I missing a constraint and/or a workaround? 2) If C# doesn't support what I'm doing, is there another way to accomplish my broader goal?

The closest I've come to finding a similar problem to my own is this, where the only answer was "This makes no sense since it doesn't leads [sic] to any limitations." So, let me spell out the exact limitation I'm facing...

Here is the non-generic version of my code; hopefully the inline comments will explain what I'm trying to do, and why:

public interface ISomeInterface
{
    string SomeFxn();
}

[Serializable]
public class SerializableInterfaceReferenceForUnity
{
    // Unity has long been able to serialize references to its own objects, but using this 
    // attribute on a "raw C#" object (i.e., anything other than Object) would try to
    // serialize the **value**.
    [SerializeField] private UnityEngine.Object unityObj;

    // This new(-ish) attribute actually allows us to serialize references ... but ONLY to
    // "raw C#" objects!  (If rawObj is a UnityEngine.Object that implements 
    // ISomeInterface, I get error messages in the console.)
    [SerializeReference] private ISomeInterface rawObj;

    // So, a kludge: branching my logic based on the type that implements ISomeInterface.
    [SerializeField] private bool objIsUnityType;

    // In my defense, I *think* the kludge will be entirely hidden on the back-end;
    // the API will "just work":
    public ISomeInterface obj
    {
        get
        {
            // FLAG: this next line works here, but breaks in generic version
            if (objIsUnityType) { return (ISomeInterface) unityObj; }
            else { return rawObj; }
        }
        set
        {
            switch (value)
            {
                case UnityEngine.Object uo:
                {
                    objIsUnityType = true;
                    unityObj = uo;
                    rawObj = null;
                    return;
                }
                default:
                {
                    objIsUnityType = false;
                    rawObj = value;
                    unityObj = null;
                    return;
                }
            }
        }
    }
}

The above is all well and good, but the real goal is to allow me to serialize ANY interface in Unity. So, here's a generic version ... which is line-for line the same, except ISomeInterface becomes TSomeInterface:

[Serializable]
public class SerializableInterfaceReferenceForUnity<TSomeInterface> 
// where TSomeInterface : { what goes here?! }
{
    [SerializeField] private UnityEngine.Object unityObj;
    [SerializeReference] private TSomeInterface rawObj;
    [SerializeField] private bool objIsUnityType;
    public TSomeInterface obj
    {
        get
        {
            //"CS0030 Cannot covert type 'UnityEngine.Object'
            // to 'TSomeInterface'" on the below:
            if (objIsUnityType) { return (TSomeInterface) unityObj; } 
            else { return rawObj; }
        }
        set
        {
            switch (value)
            {
                case UnityEngine.Object uo:
                {
                    objIsUnityType = true;
                    unityObj = uo;
                    rawObj = default;
                    return;
                }
                default:
                {
                    objIsUnityType = false;
                    rawObj = value;
                    unityObj = null;
                    return;
                }
            }
        }
    }
}

I'm crossing my fingers that all I need is a constraint on the generic type.

Barring that, is there any kind of workaround (say, some reflection magic) to change an arbitrary object to a (generic) interface type?

Barring THAT, is there any way to accomplish the same objective (namely, Unity serialization of references to objects that implement a given interface, regardless of whether those objects are Unity built-ins or raw C#) in a clean way?

Feedback / rotten tomatoes regarding my kludge are also welcome.

khancast
  • 85
  • 6
  • If I understand this (not sure), then the issue is simply that you cannot cast an object (whether generic or not) to any datatype that doesn't have a conversion method/extension known to the compiler. AFAIK, the only reason that your non-generic version works is just a quirk, because the specified interface has only a single string property (so I assume that it is just serializing it into that string). For generics, it could be any interface, so the compiler has no idea what is supposed to be done with the cast. – RBarryYoung Jul 08 '20 at 20:01
  • What you could do is cast your UnityObj into a string (ie., serialize it) and then assign that string to a required string property of the interface. Then in the Class declaration limit the generic type to only those that have that required property. – RBarryYoung Jul 08 '20 at 20:04
  • Does this answer (https://stackoverflow.com/a/8633/109122) help? It's for .Net, I don't know if it works the same for Unity. – RBarryYoung Jul 08 '20 at 20:11
  • 1
    @RBarryYoung Thanks for the comments/ideas! For the record, this is a stripped-down version of something that uses a much more complicated interface -- and that one still works, as well. Also re: that link, I was under the impression that Convert.ChangeType is pretty limited to basic types...? I was aiming for a general approach. And anyway, BFree's solution worked. – khancast Jul 08 '20 at 20:28

1 Answers1

4

I don't know anything about Unity, but assuming that UnityEngine.Object is a reference type, then you should be able to do something like this:

public class SerializableInterfaceReferenceForUnity<TSomeInterface> 
       where TSomeInterface : class
{
    public TSomeInterface obj
    {
        get
        {
            if (objIsUnityType) { return unityObj as TSomeInterface; } 
            else { return rawObj; }
        }
}

The only thing to keep in mind is that you will not be able to use any value type (struct) as the generic type for your SerializableInterfaceReferenceForUnity class.

BFree
  • 102,548
  • 21
  • 159
  • 201
  • 2
    D'oh -- I'd tried "class" as a constraint, but somehow forgot to trying casting with "as". Always the little things! Just tried it with both raw and Unity types, and it serializes beautifully. Thanks a ton! I'd upvote, but I'm a n00b so SO won't let me. – khancast Jul 08 '20 at 20:24