Reputation: 2398
I am currently having trouble with generics in C# and I have a solution, but I am looking for a cleaner one. I have generally a pretty good C# knowledge, so I am not sure, if what I want exists.
I have a generic base class, with nested constraints
public abstract class BaseParam { }
public abstract class BaseProcess<TParam> : IProcess
where TParam : BaseParam
{
public TParam Parameter { get; set; }
}
public abstract class BaseTask<TProc, TParam>
where TProc : BaseProcess<TParam>, new()
where TParam : BaseParam, new()
{
public IProcess Create()
{
var proc = new TProc();
proc.Param = new TParam();
return proc;
}
}
Now I could create a couple of derivations and this worked for a while
public class SomeParam : BaseParam { }
public class SomeProcess : BaseProcess<SomeParam> { }
public class SomeTask : BaseTask<SomeProcess, SomeParam> { }
The problem occurs when I start mixing the inheritance levels like so
public class SuperParam : SomeParam { }
// The next line won't compile
public class SuperTask : BaseTask<SomeProcess, SuperParam> { }
I know it does not compile because Covariance only exists on interfaces and Foo<A>
and Foo<B>
are technically two different classes. However the code inside would work just fine. What I want is to write a "covariant constraint" like:
public abstract class BaseTask<TProc, TParam>
where TProc : BaseProcess<TProcParam>
where TProcParam : BaseParam
where TParam : TProcParam
{
}
I tried using ISomeProc<TParam>
because interfaces are covariant, but that only works for assignments, not lower bounds. Functional Workaround is declaring an additional type argument, but that is not pretty enough:
public abstract class BaseTask<TProc, TProcParam, TParam>
where TProc : BaseProcess<TProcParam>
where TProcParam : BaseParam
where TParam : TProcParam
{
}
CompilerError:
The type 'SomeProcess' cannot be used as type parameter 'TProc' in the
generic type or method 'BaseTask<TProc,TParam>'. There is no implicit
reference conversion from 'SomeProcess' to 'BaseProc<SuperParam>'.
Edit: I understand some more explanation is required. Within the class I don't need properties of SuperParam
, only BaseParam
, but it should be an instance of SuperParam
. However we read the SuperTask
definition with reflection and create an editor. And that editor must know what object it is supposed to create.
Question: Is there any way to design the constraints of BaseTask
so that it respects covariance without adding the extra parameter TProcParam
? I could come up with workarounds, but I feel like there is an easier way.
Upvotes: 1
Views: 625
Reputation: 2398
Based on the comments and further inspection I noticed two things:
The solution I will go with for now is the a combination of the workaround and an effort to keep the default behavior clean:
// Use workaround on BaseTask
public abstract class BaseTask<TProc, TProcParam, TParam>
where TProc : BaseProcess<TProcParam>
where TProcParam : BaseParam
where TParam : TProcParam
{
}
// Generic shortcut overload
public abstract class BaseTask<TProc, TParam> : BaseTask<TProc, TParam, TParam>
where TProc : BaseProcess<TProcParam>
where TParam : TProcParam
{
}
This way I can do both:
public class SomeTask : BaseTask<SomeProcess, SomeParam> { }
public class SuperTask : BaseTask<SomeProcess, SomeParam, SuperParam> { }
Upvotes: 0
Reputation: 15982
A solution for your problem is highly dependent on how are implemented your classes, but if you can convert BaseProcess<>
to a co-variant interface IBaseProcess<>
:
public interface IBaseProcess<out TParam>
where TParam : BaseParam
{
}
And declare BaseTask
as:
public abstract class BaseTask<TProc, TParam>
where TProc : IBaseProcess<BaseParam>
where TParam : BaseParam
{
}
Then, SomeProcess
as:
public class SomeProcess : IBaseProcess<SomeParam> { }
You would be able to declare SuperTask
as you want:
public class SuperTask : BaseTask<SomeProcess, SuperParam> { }
Upvotes: 1