Thanks ahead, community!
As the title describes, I would like to cast an object that is in parent type to a child type, which is actually a child type, whilst this 'specific type' cannot be known until runtime.
Lets say I have following data holder:
public class Holder {}
public class Holder<T> : Holder
{
public T Value;
}
And this Holder (not Holder<T>) will be given to some script at runtime.
I need to cast this Holder into Holder<T> (eg, Holder<string>), so that I can access the Value : T.
For now, I can just mannually add casting cases and their coresponding methods to process it, but time by time there will be more types that goes into this Holder<T>, and it would become imposible to manage in this way.
Is there a way to accomplish this objective?
This Holder must not be flattened, as it is being used in a context as below:
public class SomeNode
{
protected Holder holder;
}
public class SomeNode<T> : SomeNode
{
public SomeNode<T>()
{
holder = new Holder<T>();
}
}
I have no clue how to approach this, nor a search keyword to catch a hint about this. Automatic suggestions came up before posting seems not my case, which were:
C# Create (or cast) objects of a specific type at runtime
C# Accessing generic Method without knowing specific type
Thanks to @W.F., I could start searching with an effective keyword 'dynamic object', and I ended up finding System.Reflection as my desired soultion.
It looks like as belows and currently it solves my immediate issue:
holder.GetType().GetProperty("GetValue").Invoke(holder, null);
But as pointed out by @OlivierJacot-Descombes, my structure and a way of using it is breaking a purpose of polymorphism. Therefore I still need a better solution, which would do a job I am looking for and also not breaking polymorphism.
Possible walkaround that comes in my head is that, first, create a method GetValue() in Holder, and also create class that inherits from Holder to implement this method:
public class Holder
{
public virtual string GetValue() => "";
}
public class Holder<T> : Holder
{
public T Value;
}
public class FloatHolder : Holder<float> //for example
{
public override string GetValue() => Value.ToString();
}
Second, change node structure like:
public class SomeNode
{
protected Holder holder;
}
public class SomeNode<T> : SomeNode {}
public class FloatNode : SomeNode<float>
{
public FloatNode()
{
holder = new FloatHolder();
}
}
Then, I can do like:
public class EchoNode : SomeNode
{
public void Tick()
{
Console.WriteLine(holder.GetValue());
}
}
Seems like too many classes are being created, but it also seems not breaking polymorphism.
Looking for further advices. Again, Thanks!
I already said this in the comment, but for better readability, I write this here as well.
Both Dynamic Object and System.Reflection were easy and fitting solutions which I was looking for, but they weren't best solutions in general.
At the beginning I was misinterpreting @OlivierJacot-Descombes 's answer. He was overall pointing out two impediments: first, my class structure is breaking polymorphism, and second, reflection is slow (and later I noticed, dynamic object as well). I didn't catch the last bit at first so I went through a long way.
Moreover, turned out, I couldn't use dynamic object for my project context, as I am not using normal C# but a Unity C#. Technically I can, but they don't blend well.
Thankfully, my revised solution was acceptable. Therefore I decided to select @OlivierJacot-Descombes 's post as an answer. But I hope, still, people would approach and leave me an good advices.
Thank you all.
If you need to cast to a specific type, you are doing polymorphism wrong. Of course you could do something like this:
switch (holder)
{
case Holder<string> stringHolder:
DoStringThing(stringHolder.Value);
break;
case Holder<int> intHolder:
DoIntThing(intHolder.Value);
break;
...
}
See also: Switch statements with patterns.
However, the idea behind polymorphism is to be able to do things without having to know the specific type. Therefore, re-design the holder classes and have them do the type specific thing themselves:
public abstract class Holder
{
public abstract void DoThing();
}
public abstract class Holder<T> : Holder
{
public abstract T Value { get; }
}
Some examples of specific types:
public class StringHolder : Holder<string>
{
public StringHolder(string value)
{
Value = value;
}
public override string Value { get; }
public override void DoThing()
{
Console.WriteLine($"String of length {Value.Length} is \"{Value}\"");
}
}
public class IntHolder : Holder<int>
{
public IntHolder(int value)
{
Value = value;
}
public override int Value { get; }
public override void DoThing()
{
Console.WriteLine($"The integer {Value} is {(Value % 2 == 0 ? "even" : "odd")}");
}
}
Now you can simply write
holder.DoThing();
... without having to cast.
Update
Your edited question indeed shows a polymorphic version.
Here I want to present another approach which merges Holder
and Holder<T>
in a single class through the use of interfaces.
public interface IHolder
{
object Value { get; set; }
}
public interface IHolder<T> : IHolder
{
new T Value { get; set; } // The new keyword hides the inherited property.
}
public class Holder<T> : IHolder<T>
{
object IHolder.Value
{
get => Value; // Returns T Holder<T>.Value as object.
set => Value = value is T t ? t : default; // Sets T Holder<T>.Value.
}
public T Value { get; set; }
}
Holder<T>
now implements a "neutral" Value
property declared in IHolder
based on the object
type. Since it implements it explicitly (i.e., instead of public object Value
we write object IHolder.Value
), this property is hidden, unless it is accessed through the interface. This allows you, for example, to declare a List<IHolder>
and to retrieve different kinds of Holder<T>
values with list[i].Value
as object.
But you have a variable Holder<float> floatHolder
, you can get the strongly typed float
value.
Note that this still allows you do derive more specific types like class FloatHolder : Holder<float>
, but it might not even be necessary.
If you intend to work only with derived types, you can mark Holder<T>
as abstract and also all the members that must be implemented by the deriving classes. This makes it impossible to create an instance of Holder<T>
with new
and also allows you to declare abstract methods without body.