Generic method type conversion

Viewed 46

I have something like this:

interface IArgument{ ... }

class ArgumentA : IArgument{ ... }

abstract class Parameter<T> where T : class, IArgument{ 
   ...
   abstract decimal GetValue(T args);
   ...
}

class ParameterA : Parameter<ArgumentA>{
   override decimal GetValue(ArgumentA args){
      ...
   }
}

abstract class Calculation{
   List<Parameter<IArgument>> Parameters{get;set;}
   
   decimal FindParameterValue<T>() where T : Parameter<IArgument>{
      ...
   }
}

The method FindParameterValue is where I have a problem. It gets the first parameter of a specific type out of the list and returns its value. Now I get this error: Error CS0311 The type 'ParameterA' cannot be used as type parameter 'T' in the generic type or method 'Calculation.FindParameterValue<T>()'. There is no implicit reference conversion from 'ParameterA' to 'Parameter<IArgument>'. . I would like to just supply a derived class which implements Parameter< IArgument>. I can avoid it by doing this:

public decimal FindParameterValue<T,U>() where T : Parameter<U> where U : class, IArgument

Which causes me to call it like:

FindParameterValue<ParameterA, ArgumentA>();

But I'd like to call it with just the 'ParameterA'-type.

Sorry if this is already solved somewhere.

Thanks in advance!

2 Answers

The constraint uses the actual types. If the rules worked as you describe it might very well be possible to write:

void MyMethod<T>(T myParameter) where T : List<IFruit>{
    myParameter.Add( new Banana() );
}
....
MyMethod(new List<Apple>());

And you would end up with a list of apples that contains a banana. You can avoid this by changing the abstract Parameter class to a covariant interface, i.e.

interface IArgument { }
class ArgumentA : IArgument {}
interface IParameter<out T> where T  :IArgument{ }
class ParameterA : IParameter<ArgumentA>{}

class Calculation
{
    void FindParameterValue<T>() where T : IParameter<IArgument>
    { }

    void Test()
    {
        FindParameterValue<ParameterA>();
    }
}

The problem is that every definition of Parameter<T> is a different type. If you remove the generics from the abstract class you will solve your problem. Is there a reason Parameter needs to be generic? Can you work with pattern matching instead?

Although the intention isn't clear exactly, try the code below:

public interface IArgument { }

public class ArgumentA : IArgument
{
    public decimal Value { get; }
}

public abstract class Parameter
{
    public abstract decimal GetValue(IArgument args);
}

public class ParameterA : Parameter
{
    public override decimal GetValue(IArgument args)
    {
        if (args is ArgumentA argA)
        {
            return argA.Value;
        }
        return 0m;
    }
}

public abstract class Calculation
{
    List<Parameter> Parameters { get; } = new List<Parameter>();

    public decimal FindParameterValue<T>(IArgument argument) where T : Parameter
    {
        var item = Parameters.OfType<T>().FirstOrDefault();
        if (item != null)
        {
            return item.GetValue(argument);
        }
        return 0m;
    }
}
Related