我很难描述标题中的问题,如果标题不是最清楚的话,对不起。
假设我有以下接口/类:
public interface IAction { /*...*/ }
public class WriteAction : IAction { /*...*/ }
public class ReadAction : IAction { /*...*/ }
现在,这些操作将在其他类中使用。 ReadWriteTest
可以包含ReadAction
和WriteAction
个对象,但ReadTest
只能包含ReadAction
个对象。
请注意,这些测试对象不仅仅是这个,我正在编写其他功能,因为它们与问题无关。
两个xxxTest
类共享一个公共接口。所以接口和ReadWriteTest
类的实现如下:
public interface ITest
{
List<IAction> Actions { get; set; }
}
public class ReadWriteTest : ITest
{
public List<IAction> Actions { get; set; }
}
问题是对于ReadTest
类,我想将此属性限制为仅包含ReadAction
个元素。
我尝试实施的内容如下:
public class ReadTest : ITest
{
public List<ReadAction> Actions { get; set; }
}
在我看来,这应该有效,因为每个ReadAction
都固有地实现了IAction
接口。
但是,编译器不喜欢它,并告诉我ReadTest
类没有实现所有必需的IAction
属性。
有没有办法在ReadTest
类定义中限制此Actionlist的内容?
我可以通过创建一个自定义AddAction(IAction action)
方法来解决这个问题,该方法根本不添加任何WriteAction
个对象,但我希望能更优雅地解决这个问题。
这可能吗?
最终结果的更新
正如所回答的,可以通过向接口添加泛型类型来完成。这解决了我所描述的问题,但遗憾的是没有解决更大的问题。添加此generict类型意味着我现在无法将Test对象作为ITest
来解决,因为我现在需要指定泛型参数,这对于任一测试都是不同的。
解决方案(或至少我的解决方案)是从Actions
接口中删除ITest
属性。 Actions
属性仍然存在于类中
我没有在界面中定义list属性,而是在界面中添加了Run()
方法。此方法将迭代Test类中的本地定义的Actions
列表。
快速概述:
foreach(ITest myTest in myTests)
{
myTest.Run()
}
ReadWriteTest
的类实现:
public List<IAction> Actions {get; set;}
public void Run()
{
foreach(IAction action in Actions) { /*...*/ }
}
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.
{ /*...*/ }
}
答案 0 :(得分:6)
您可以通过在ITest
界面中使用通用类型来解决此问题:
public interface ITest<T> where T : IAction
{
List<T> Actions { get; set; }
}
请注意强制传入类型为IAction
的类型约束。这反过来使您的子类成为:
public class ReadWriteTest : ITest<IAction>
{
public List<IAction> Actions { get; set; }
}
public class ReadTest : ITest<ReadAction>
{
public List<ReadAction> Actions { get; set; }
}
答案 1 :(得分:2)
如果您可以接受更改界面以便无法通过属性直接设置操作,则可以使用out
关键字使类变为协变。
这将允许您创建一个类型为ReadWriteTest
的对象,并将其传递给接受ITest<IAction>
类型参数的方法。
这是一个完整的可编辑控制台应用程序,用于演示:
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();
}
}
}
}
如果您希望能够在创建对象后设置动作,则可以为每个具体类添加一个setter方法。
例如,您可以将ReadWriteTest
类更改为:
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>();
}