How to override an interface property with an inherited type
I find it difficult to describe the problem in the title, sorry if the title is not the clearest.
Suppose I have the following interface / classes:
public interface IAction { /*...*/ }
public class WriteAction : IAction { /*...*/ }
public class ReadAction : IAction { /*...*/ }
These actions will now be used in other classes. ReadWriteTest
can have objects ReadAction
and WriteAction
, but ReadTest
can only have objects ReadAction
.
Please note that these test objects are more than just this, I am editing other functions as they are not relevant to the question.
Both classes xxxTest
share a common interface. Thus, the implementation of the interface and class ReadWriteTest
looks like this:
public interface ITest
{
List<IAction> Actions { get; set; }
}
public class ReadWriteTest : ITest
{
public List<IAction> Actions { get; set; }
}
The problem is, for a class, ReadTest
I would like to restrict this property to only containing ReadAction
elements.
I tried to implement the following:
public class ReadTest : ITest
{
public List<ReadAction> Actions { get; set; }
}
It seems to me that this should work, since everyone ReadAction
essentially implements an interface IAction
.
However, he doesn't like the compiler and says that the class ReadTest
does not implement all the required properties IAction
.
Is there a way to limit the content of this list of actions in the class definition ReadTest
?
I can get around this by creating my own AddAction(IAction action)
method that just doesn't add any objects WriteAction
, but I was hoping for a more elegant solution to this problem.
Is it possible?
Final Result Update
As mentioned, this can be done by adding a generic type to the interface. This solves the problem I am describing, but unfortunately does not solve the larger problem. The addition of this typical type means that now I cannot address the test object as ITest
, since now I need to specify a general parameter that is different for any test.
The solution (or at least my solution) is to remove the property Actions
from the interface ITest
. Properties Actions
still exist in classes.
Instead of having a list property defined in the interface, I added a method to the interface Run()
. This method will iterate over a locally defined list Actions
in any test class.
So, as a quick overview:
foreach(ITest myTest in myTests)
{
myTest.Run()
}
Class execution for ReadWriteTest
:
public List<IAction> Actions {get; set;}
public void Run()
{
foreach(IAction action in Actions) { /*...*/ }
}
Class execution for ReadTest
:
public List<ReadAction> Actions {get; set;}
public void Run()
{
foreach(IAction action in Actions) //I could also declare it as a ReadAction. Either works.
{ /*...*/ }
}
source to share
If you can accept the interface change so that you cannot directly set actions via a property, you can make the class covariant using a keyword out
.
This will then allow you to create, for example, an object of type ReadWriteTest
and pass it to a method that takes a type parameter ITest<IAction>
.
Here's a complete compiled console application for demo:
using System;
using System.Collections.Generic;
namespace Demo
{
// The basic IAction interface.
public interface IAction
{
void Execute();
}
// Some sample implementations of IAction.
public sealed class ReadAction: IAction
{
public void Execute()
{
Console.WriteLine("ReadAction");
}
}
public sealed class ReadWriteAction: IAction
{
public void Execute()
{
Console.WriteLine("ReadWriteAction");
}
}
public sealed class GenericAction: IAction
{
public void Execute()
{
Console.WriteLine("GenericAction");
}
}
// The base ITest interface. 'out T' makes it covariant on T.
public interface ITest<out T> where T: IAction
{
IEnumerable<T> Actions
{
get;
}
}
// A ReadWriteTest class.
public sealed class ReadWriteTest: ITest<ReadWriteAction>
{
public ReadWriteTest(IEnumerable<ReadWriteAction> actions)
{
_actions = actions;
}
public IEnumerable<ReadWriteAction> Actions
{
get
{
return _actions;
}
}
private readonly IEnumerable<ReadWriteAction> _actions;
}
// A ReadTest class.
public sealed class ReadTest: ITest<ReadAction>
{
public ReadTest(IEnumerable<ReadAction> actions)
{
_actions = actions;
}
public IEnumerable<ReadAction> Actions
{
get
{
return _actions;
}
}
private readonly IEnumerable<ReadAction> _actions;
}
// A GenericTest class.
public sealed class GenericTest: ITest<IAction>
{
public GenericTest(IEnumerable<IAction> actions)
{
_actions = actions;
}
public IEnumerable<IAction> Actions
{
get
{
return _actions;
}
}
private readonly IEnumerable<IAction> _actions;
}
internal class Program
{
private static void Main()
{
// This demonstrates that we can pass the various concrete classes which have
// different IAction types to a single test method which has a parameter of
// type ITest<IAction>.
var readActions = new[]
{
new ReadAction(),
new ReadAction()
};
var test1 = new ReadTest(readActions);
test(test1);
var readWriteActions = new[]
{
new ReadWriteAction(),
new ReadWriteAction(),
new ReadWriteAction()
};
var test2 = new ReadWriteTest(readWriteActions);
test(test2);
var genericActions = new[]
{
new GenericAction(),
new GenericAction(),
new GenericAction(),
new GenericAction()
};
var test3 = new GenericTest(genericActions);
test(test3);
}
// A generic test method.
private static void test(ITest<IAction> data)
{
foreach (var action in data.Actions)
{
action.Execute();
}
}
}
}
If you want to be able to set actions after objects are created, you can add a setter method to each specific class.
For example, you can change the class ReadWriteTest
to:
public sealed class ReadWriteTest: ITest<ReadWriteAction>
{
public void SetActions(IEnumerable<ReadWriteAction> actions)
{
_actions = actions;
}
public IEnumerable<ReadWriteAction> Actions
{
get
{
return _actions;
}
}
private IEnumerable<ReadWriteAction> _actions = Enumerable.Empty<ReadWriteAction>();
}
source to share
You can fix this by using a generic type in your interface ITest
:
public interface ITest<T> where T : IAction
{
List<T> Actions { get; set; }
}
Notice the type constraint that the passed type enforces IAction
. This in turn makes your subclasses look like this:
public class ReadWriteTest : ITest<IAction>
{
public List<IAction> Actions { get; set; }
}
public class ReadTest : ITest<ReadAction>
{
public List<ReadAction> Actions { get; set; }
}
source to share