C
C#2y ago
Shimizoki

❔ [Unity?] How to have a list of generic classes in inspector cast as derived class

I have some classes as follows:
c#
[System.Serializable]
public class TrackedMarkers<T, U>
where T : MarkerScriptable
where U : MarkerInterface
{
string tag;
public T t;
public U u;
}

public abstract class MarkerInterface : MonoBehaviour {
public virtual void myFunc() {}
}

public class Marker1 : MarkerInterface {
public int color;
public override void myFunc() {}

}


public class Marker2 : MarkerInterface {
public int count;
public override void myFunc() {}

}



public class MarkerScriptable : ScriptableObject {
public int[] ids = new int[0];
}


public class Scriptable1 : MarkerScriptable { /* unique stuff */ }

public class Scriptable2 : MarkerScriptable { /* unique stuff */ }
c#
[System.Serializable]
public class TrackedMarkers<T, U>
where T : MarkerScriptable
where U : MarkerInterface
{
string tag;
public T t;
public U u;
}

public abstract class MarkerInterface : MonoBehaviour {
public virtual void myFunc() {}
}

public class Marker1 : MarkerInterface {
public int color;
public override void myFunc() {}

}


public class Marker2 : MarkerInterface {
public int count;
public override void myFunc() {}

}



public class MarkerScriptable : ScriptableObject {
public int[] ids = new int[0];
}


public class Scriptable1 : MarkerScriptable { /* unique stuff */ }

public class Scriptable2 : MarkerScriptable { /* unique stuff */ }
In my main script I am attempting to have a list of Tracked Markers in the inspector, But if I initialize my list as 

c#
public List<TrackedMarkers<MarkerScriptable, MarkerInterface>> myList;

// In inspector I slot
// myList[0] = (Marker1, Scriptable1)
// myList[1] = (Marker2, Scriptable2)
c#
public List<TrackedMarkers<MarkerScriptable, MarkerInterface>> myList;

// In inspector I slot
// myList[0] = (Marker1, Scriptable1)
// myList[1] = (Marker2, Scriptable2)

 Then when I try and cast it later, I get the expected

c#
string t = myList[0].tag;
// Above line works
var tm = myList[0] as TrackedMarker<Scriptable1, Marker1>;

// error CS0039: Cannot convert type 'TrackedMarkers<MarkerScriptable, MarkerInterface>' to 'TrackedMarkers<Scriptable1, Marker1>' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion

c#
string t = myList[0].tag;
// Above line works
var tm = myList[0] as TrackedMarker<Scriptable1, Marker1>;

// error CS0039: Cannot convert type 'TrackedMarkers<MarkerScriptable, MarkerInterface>' to 'TrackedMarkers<Scriptable1, Marker1>' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion



 I somewhat understand why… If it was stored in the baseClass list, it would not know which child it belongs to. However in unity the objects that I am slotting in are already the derived class… so it should be available.
32 Replies
Shimizoki
ShimizokiOP2y ago
Examples online that have changed TrackedMarkers as seen below have not worked for me, but perhaps I misunderstood the full implementation.


c#
public interface TrackedMarkers {}

[System.Serializable]
public class TrackedMarkers<T, U> : TrackedMarkers
where T : MarkerScriptable
where U : MarkerInterface
{
string tag;
public T t;
public U u;
}


// In the main script
public List<TrackedMarkers> myList;

// Same Cast
string t = myList[0].tag;
// 'TrackedMarkers' does not contain a definition for 'tag' and no accessible extension method 'tag' accepting a first argument of type 'TrackedMarkers' could be found
var tm = myList[0] as TrackedMarker<Scriptable1, Marker1>;

// Does not throw a compile error... but that might be because it is failing on the above line
c#
public interface TrackedMarkers {}

[System.Serializable]
public class TrackedMarkers<T, U> : TrackedMarkers
where T : MarkerScriptable
where U : MarkerInterface
{
string tag;
public T t;
public U u;
}


// In the main script
public List<TrackedMarkers> myList;

// Same Cast
string t = myList[0].tag;
// 'TrackedMarkers' does not contain a definition for 'tag' and no accessible extension method 'tag' accepting a first argument of type 'TrackedMarkers' could be found
var tm = myList[0] as TrackedMarker<Scriptable1, Marker1>;

// Does not throw a compile error... but that might be because it is failing on the above line
It looks like a Covariance problem
Anton
Anton2y ago
yeah it has nothing to do with unity also don't use as use casts where possible
Shimizoki
ShimizokiOP2y ago
I figured it didn't but just in case the issue was related to unity, I wanted to make it known Changing to casts does not fix the error, but it does change it. error CS0030: Cannot convert type 'TrackedMarkers<MarkerScriptable, MarkerInterface>' to 'TrackedMarkers<Scriptable1, Marker1>'
lycian
lycian2y ago
that's because as does a runtime attempt, and returns null. Explicit cast lets the compiler check the covariance/contravariance problem for you. You'll need to use an interface for contravariance :
abstract class MarkerInterface
{
}

abstract class MarkerScriptable
{
}

class Marker1 : MarkerInterface
{
}

class Marker2 : MarkerInterface
{
}

class Scriptable1 : MarkerScriptable
{
}

class Scriptable2 : MarkerScriptable
{
}



interface ITrackedMarkers<out T, out U>
where T : MarkerScriptable
where U : MarkerInterface
{
string tag { get; }
public T t { get; }
public U u { get; }
}

class Program
{
static void Main()
{
var list = new System.Collections.Generic.List<ITrackedMarkers<MarkerScriptable, MarkerInterface>>();
var casted = (ITrackedMarkers<Scriptable1, Marker1>)list[0];
}
}
abstract class MarkerInterface
{
}

abstract class MarkerScriptable
{
}

class Marker1 : MarkerInterface
{
}

class Marker2 : MarkerInterface
{
}

class Scriptable1 : MarkerScriptable
{
}

class Scriptable2 : MarkerScriptable
{
}



interface ITrackedMarkers<out T, out U>
where T : MarkerScriptable
where U : MarkerInterface
{
string tag { get; }
public T t { get; }
public U u { get; }
}

class Program
{
static void Main()
{
var list = new System.Collections.Generic.List<ITrackedMarkers<MarkerScriptable, MarkerInterface>>();
var casted = (ITrackedMarkers<Scriptable1, Marker1>)list[0];
}
}
Shimizoki
ShimizokiOP2y ago
I am working through the changes you advised, while I am going through the following situation showed up. This function is in ITrackedMarkers
interface ITrackedMarkers<out T, out U>
where T : MarkerScriptable
where U : MarkerInterface
{
string tag { get; }
public T[] t { get; }
public U[] u { get; } // The U here is an array

void Add(U u) {
//add u to this.u
}
}

// error CS1961: Invalid variance: The type parameter 'U' must be contravariantly valid on 'ITrackedMarkers<T, U>.Add(U)'. 'U' is covariant.
interface ITrackedMarkers<out T, out U>
where T : MarkerScriptable
where U : MarkerInterface
{
string tag { get; }
public T[] t { get; }
public U[] u { get; } // The U here is an array

void Add(U u) {
//add u to this.u
}
}

// error CS1961: Invalid variance: The type parameter 'U' must be contravariantly valid on 'ITrackedMarkers<T, U>.Add(U)'. 'U' is covariant.
Which I think is impossible? From my reading I cant take U as an In and Out, but all I am trying to do is add it to the list Sorry for not showing that up front... I did not realize it would make a difference.
lycian
lycian2y ago
oh yea sorry, I don't think out is actually needed
abstract class MarkerInterface
{
}

abstract class MarkerScriptable
{
}

class Marker1 : MarkerInterface
{
}

class Marker2 : MarkerInterface
{
}

class Scriptable1 : MarkerScriptable
{
}

class Scriptable2 : MarkerScriptable
{
}



interface ITrackedMarkers<T, U>
where T : MarkerScriptable
where U : MarkerInterface
{
string tag { get; }
public T t { get; }
public U u { get; }

void Add(U u);
}

class TrackedMarkers<T, U> : ITrackedMarkers<T, U>
where T : MarkerScriptable
where U : MarkerInterface
{
public string tag {get; set;}
public T t {get; set;}
public U u {get; set;}

public void Add(U u)
{
}
}

class Program
{
static void Main()
{
var list = new System.Collections.Generic.List<TrackedMarkers<MarkerScriptable, MarkerInterface>>();
var casted = (ITrackedMarkers<Scriptable1, Marker1>)list[0];
}
}
abstract class MarkerInterface
{
}

abstract class MarkerScriptable
{
}

class Marker1 : MarkerInterface
{
}

class Marker2 : MarkerInterface
{
}

class Scriptable1 : MarkerScriptable
{
}

class Scriptable2 : MarkerScriptable
{
}



interface ITrackedMarkers<T, U>
where T : MarkerScriptable
where U : MarkerInterface
{
string tag { get; }
public T t { get; }
public U u { get; }

void Add(U u);
}

class TrackedMarkers<T, U> : ITrackedMarkers<T, U>
where T : MarkerScriptable
where U : MarkerInterface
{
public string tag {get; set;}
public T t {get; set;}
public U u {get; set;}

public void Add(U u)
{
}
}

class Program
{
static void Main()
{
var list = new System.Collections.Generic.List<TrackedMarkers<MarkerScriptable, MarkerInterface>>();
var casted = (ITrackedMarkers<Scriptable1, Marker1>)list[0];
}
}
Shimizoki
ShimizokiOP2y ago
so my interface needs every variable / function prototype? but the main logic stays in the class?
lycian
lycian2y ago
you can technically do it in the interface with default implementations in interfaces or even just cast to the concrete implementation
(TrackedMarkers<U, T>)ITrackedMarkers<U,T>
(TrackedMarkers<U, T>)ITrackedMarkers<U,T>
is a valid cast
Shimizoki
ShimizokiOP2y ago
You use a lot of setters, and it looks like I need to to that in both the class, and it's interface. This creates a problem for fuction calls that use ref. Is there a better workaround than lots of temp vars?
Anton
Anton2y ago
ref getter property there's no other good way to achieve that PropertyInfo is a thing, but don't use it for that
Shimizoki
ShimizokiOP2y ago
Have an example? a quick google does not show me much. I am not used to using properties since they don't play super nice with unity
public string tag { get => this._tag; set => this._tag = value; }
private string _tag;
public string tag { get => this._tag; set => this._tag = value; }
private string _tag;
Something like this? then pass the _tag by ref?
Anton
Anton2y ago
no
interface A
{
ref int a { get; }
}

class B : A
{
private int _a;
public ref int a => ref _a;
}
interface A
{
ref int a { get; }
}

class B : A
{
private int _a;
public ref int a => ref _a;
}
or don't use ref and just switch to properties add [field: SerializeField] on the property declarations then it's the same editor experience as fields
Shimizoki
ShimizokiOP2y ago
You all were able to help me get it back to compiling... which is great! But, runtime casts are still no good.
class Program
{
[SerializeField] List<TrackedMarkers<MarkerScriptable, MarkerInterface>> _trackerList;

void Start() {
var casted = (ITrackedMarkers<Scriptable1, Marker1>)_trackerList[0];
// InvalidCastException: Specified cast is not valid.
}
}
class Program
{
[SerializeField] List<TrackedMarkers<MarkerScriptable, MarkerInterface>> _trackerList;

void Start() {
var casted = (ITrackedMarkers<Scriptable1, Marker1>)_trackerList[0];
// InvalidCastException: Specified cast is not valid.
}
}
As a note, I set the first element in the list (in editor) to be of the correct type
Anton
Anton2y ago
ah the guy that wrote the examples above forgot to put out on the generics but ref won't work with that you have to do get set
Shimizoki
ShimizokiOP2y ago
out does not work with the Add(U u) function which takes a U as a parameter. That's why they removed it I added out to both T and U, but the same runtime exception exists
Anton
Anton2y ago
yes it won't you can't return an array either because it's mutable change the array type to ReadOnlyCollection I think, then it will work read the error bruh it's about nullability
Shimizoki
ShimizokiOP2y ago
I saw warnings about nullability, but perhaps I am misunderstanding the error there.
ero
ero2y ago
isn't that an upcast? how would that work? the list quite literally is not an ITrackedMarkersRepo<Scriptable1, Marker1> (U is not Marker1)
Shimizoki
ShimizokiOP2y ago
i am not sure I am following. I was under the impression that co/contravariance worked in exactly that fashion.
ero
ero2y ago
you don't specify a variance on U
Shimizoki
ShimizokiOP2y ago
that is because U when set as a read only collection seems to state that it needs to be invariant, I could not find a way to set it to out If I were to use an IReadOnlyCollection<U> I am able to use out U in ITrackedMarkers<out T, out U>. But I get the same runtime invalid cast exception.
Anton
Anton2y ago
that interface has nothing to do with the repo interface you're casting to the repo interface for which U is not out that won't ever work if you want to add items in such generic way, you have to either hide it polymorphically under the object you're trying to add, or make another class that's responsible for adding items, or make a method that takes object and tries casting to U you can't make that work with interfaces that said, if you make U on the repo interface in, you'll be able to add a more derived object no problem but there's no benefit to that in your case if you stop and think about it carefully, you'll see it makes sense and whatever you're trying to do (that add thing) doesn't, from the type safety point of view
Shimizoki
ShimizokiOP2y ago
I am absolutely happy to change as many things as necessary here. I even have some ability to change the actual functionality of the class. Maybe I can find a way to not use that add function at all. I'm just really looking for a way that I can have all of these different objects in an enumerable of some kind The thing that I'm working on is about to go down that nasty rabbit hole of having 50 of the exact same item, with no changes between any of them except for the types. Each of these items has a boilerplate copy and paste, and then in half a dozen places scattered around the code base needs to have the exact same chunk of code copied and then the names changed for this item. My goal is to prevent that. Maybe my starting premise was wrong to begin with
Anton
Anton2y ago
if it's copy and paste, then use composition you can pull common things out into another type, and have a field of that type
Shimizoki
ShimizokiOP2y ago
So rather than having a base : derived... I have a base<T> and the T is my fake derived? I still don't see how that helps me get a list of all the types Ah wait... I think I am seeing where you are going
Anton
Anton2y ago
what
class A {}
class B {}
class IA{A a {get;}}
class IB{B b {get;}}
class C : IA, IB
{
A a {get;}
B b {get;}
}
class A {}
class B {}
class IA{A a {get;}}
class IB{B b {get;}}
class C : IA, IB
{
A a {get;}
B b {get;}
}
congrats, you just derived from both a and b but in all seriousness, use the component system either the behavior one, or the data oriented one either one is designed to help with that
Accord
Accord2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server