Generic class polymorphism
If I have the following:
public abstract class Parameter<T>
{
protected T value;
public virtual T Value
{
get { return value; }
set { this.value = value; }
}
protected Parameter(T startingValue)
{
value = startingValue;
}
}
public class FloatParameter : Parameter<float>
{
public FloatParameter(float startingValue) : base(startingValue){}
}
public class IntParameter : Parameter<int>
{
public override int Value
{
get { return value; }
set { this.value = value > 100 ? 100 : value; }
}
public IntParameter(int startingValue) : base (startingValue) {}
}
Is there a way to create some List<Parameter>
that can contain any of the derived types? For example, something like:
// no type specified in Parameter
List<Parameter> storedParameters = new List<Parameter>();
storedParameters.Add(new FloatParameter(2f));
storedParameters.Add(new IntParameter(7));
foreach(Parameter p in storedParameters)
{
DoSomethingWithValue(p.Value);
}
Or, conversely, if this implementation is flawed, is there a better way to do it? What I have here looks a little naive.
source to share
The only way I can handle a case like this is to have an interface that you use to manipulate generic types, something like this should work:
public interface IParameter
{
void DoSomething();
}
public abstract class Parameter<T>
{
protected T value;
public T Value
{
get { return value; }
set { this.value = value; }
}
protected Parameter(T startingValue)
{
value = startingValue;
}
}
public class FloatParameter : Parameter<float>, IParameter
{
public FloatParameter(float startingValue) : base(startingValue) { }
public void DoSomething()
{
Console.WriteLine(value);
}
}
public class IntParameter : Parameter<int>, IParameter
{
public IntParameter(int startingValue) : base(startingValue) { }
public void DoSomething()
{
Console.WriteLine(value);
}
}
In your case, you can create a list of IParameter interface and add specific instances there:
var list = new List<IParameter>();
list.Add(new FloatParameter(1F));
list.Add(new IntParameter(1));
foreach (var item in list)
{
item.DoSomething();
}
source to share
Try adding a non-structural interface. Here's an example:
public class Program
{
static void Main(string[] args)
{
try
{
List<IParameter> storedParameters = new List<IParameter>();
storedParameters.Add(new FloatParameter(2f));
storedParameters.Add(new IntParameter(7));
foreach (IParameter p in storedParameters)
{
Console.WriteLine(p.ToString());
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
public interface IParameter
{
object value { get; }
}
public class Parameter<T> : IParameter
{
public object value { get; protected set; }
public virtual T Value
{
get { return (T)value; }
set { this.value = value; }
}
protected Parameter(T startingValue)
{
value = startingValue;
}
}
public class FloatParameter : Parameter<float>
{
public FloatParameter(float startingValue) : base(startingValue){ }
}
public class IntParameter : Parameter<int>
{
public override int Value
{
get { return (int)value; }
set { this.value = value > 100 ? 100 : value; }
}
public IntParameter(int startingValue) : base (startingValue) { }
}
source to share
If you change the value to an object, you can set the value to whatever type you like:
class Program
{
static void Main(string[] args)
{
// no type specified in Parameter
var storedParameters = new List<ParameterBase>();
storedParameters.Add(new FloatParameter(3.5F));
storedParameters.Add(new IntParameter(7));
foreach (var p in storedParameters)
{
Console.WriteLine(p.Value);
}
}
}
public class ParameterBase
{
protected object value;
public virtual object Value
{
get { return value; }
set { this.value = value; }
}
}
public class FloatParameter : ParameterBase
{
public FloatParameter(float value)
{
Value = value;
}
}
public class IntParameter : ParameterBase
{
public IntParameter(int value)
{
Value = value;
}
}
UPDATED: Use object instead of dynamic and remote ValueType as suggested by @Pieter Witvoet
source to share
No, it cannot be done.
What you are trying to do is have an interface (or base class) that emits a property of type undefined, so that you can then retrieve that value and dynamically dispatch it to the correct override DoSomethingWithValue
.
What you need is achievable by defining the property as dynamic
instead of using generics.
public class Parameter
{
protected dynamic value;
public dynamic Value
{
get { return value; }
set { this.value = value; }
}
public Parameter(dynamic startingValue)
{
value = startingValue;
}
}
public class MyStuff {
public void DoStuff()
{
List<Parameter> storedParameters = new List<Parameter>();
storedParameters.Add(new Parameter(2f));
storedParameters.Add(new Parameter(7));
foreach (Parameter p in storedParameters)
{
DoSomethingWithValue(p.Value);
}
}
}
Otherwise, you should look into the implementation Double dispatch
.
source to share
You can do this by specifying a common interface and using a visitor template.
public interface IParameterVisitor
{
void VisitInt(int value);
void VisitFloat(float value);
}
public interface IParameter
{
void Accept(IParameterVisitor visitor);
}
The previous implementation should be slightly modified:
public abstract class Parameter<T> : IParameter
{
protected T value;
public virtual T Value
{
get { return value; }
set { this.value = value; }
}
protected Parameter(T startingValue)
{
value = startingValue;
}
public abstract void Accept(IParameterVisitor visitor);
}
FloatParameter
will VisitFloat
, but IntParameter
willVisitInt
public class FloatParameter : Parameter<float>
{
public FloatParameter(float startingValue) : base(startingValue) { }
public override void Accept(IParameterVisitor visitor)
{
visitor.VisitFloat(this.value);
}
}
public class IntParameter : Parameter<int>
{
public override int Value
{
get { return value; }
set { this.value = value > 100 ? 100 : value; }
}
public override void Accept(IParameterVisitor visitor)
{
visitor.VisitInt(this.value);
}
public IntParameter(int startingValue) : base(startingValue) { }
}
And our visitor for example:
public class MyVisitor : IParameterVisitor
{
public void VisitInt(int value)
{
Console.WriteLine($"Visiting an int: {value}");
}
public void VisitFloat(float value)
{
Console.WriteLine($"Visiting a float: {value}");
}
}
Finally, using:
var parameters =
new List<IParameter> {new FloatParameter(0.5f), new IntParameter(1)};
var visitor = new MyVisitor();
foreach (IParameter parameter in parameters) {
parameter.Accept(visitor);
}
source to share