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.

+3


source to share


5 answers


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();
 }

      

+3


source


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) { }
}

      

+2


source


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

+1


source


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

.

+1


source


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);
}

      

+1


source







All Articles