Reputation: 3422
I have a question regarding generics in C#. I would like to treat a generic class/interface as one type in any collection and be able to execute the method in foreach
loop.
public interface IRunner<T> where T : struct
{
void Run(T data);
}
public class FooRunner : IRunner<int>
{
public void Run(int data)
{
throw new NotImplementedException();
}
}
public class BarRunner : IRunner<float>
{
public void Run(float data)
{
throw new NotImplementedException();
}
}
Having above structure I cannot assign instances of IRunner into one collection because I would need to provide type
var runners = new List<IRunner<?>>();
What I can do it to create non-generic IRunner interface then I would be able to assign them into collection
public interface IRunner { }
public interface IRunner<T> : IRunner where T : struct
{
void Run(T data);
}
Above construct allows me to gather the runners:
var runners = new List<IRunner> { new BarRunner(), new FooRunner() };
Since IRunner does not contain any method I am not able to execte Run() for any memebers which I would like to.
I know that this might be achived using reflections but I see it as a error-prone and dirty solution.
Is there anyway to achive solution like below:
var runners = new List<IRunner<?>> { new BarRunner(), new FooRunner() };
foreach (var runner in runners)
{
T data = GetData(); // get some data
runner.Run(data);
}
Upvotes: 0
Views: 116
Reputation: 2064
To be able to Run
with dynamic types, you may refactor as below. However there is no type checking during compile time, and the app may crash during runtime if inappropriate argument type is passed during method invocation.
public interface IRunner
{
void Run(object value);
}
public interface IRunner<T>:IRunner
{
void Run<T>(T value);
}
public abstract class BaseRunner<T>:IRunner<T>
{
public void Run(object value)
{
if(!(value is T arg))
throw new ArugmentException($"{nameof(value)} must be of type {typeof(T)});
Run(arg);
}
public abstract void Run(T value);
}
public class FooRunner:BaseRunner<int>
{
public override void Run(int value)
{
// your implementation
}
}
Usage:
var runners = new List<IRunner>{new FooRunner(),new BarRunner()};
foreach(var runner in runners) runner.Run(arg);
Upvotes: 1
Reputation: 11
I can't think of a way of achieving that without doing some boxing or reflection or even loosing type-safety (as highlighted on previous answers), but if your use case allows it, you can instead of having a generic interface IRunner you could have a generic Run method with the struct generic constraint. This will allow you to have a generic store and worry about the value type only when calling the Run method.
public interface IRunner
{
void Run<T>(T data) where T: struct;
}
Upvotes: 0
Reputation: 143363
Best option(at least in terms of readability) I can suggest in this particular case is to use Enumerable.OfType<TResult>
method for type check in pair with non-generic IRunner
interface:
int i = 1;
foreach (var runner in runners.OfType<IRunner<int>>())
{
runner.Run(i);
}
Upvotes: 0