【问题标题】:How to override an interface property with an inherited type如何用继承的类型覆盖接口属性
【发布时间】:2014-11-21 08:28:14
【问题描述】:

我很难描述标题中的问题,如果标题不是最清楚的,请见谅。

假设我有以下接口/类:

public interface IAction { /*...*/ }

public class WriteAction : IAction { /*...*/ }
public class ReadAction : IAction { /*...*/ }

现在,这些操作将用于其他类。 ReadWriteTest 可以同时拥有 ReadActionWriteAction 对象,但 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 对象,但我希望有一个更优雅的解决方案来解决这个问题。

这可能吗?

最终结果更新

正如回答的那样,可以通过向接口添加泛型类型来完成。这解决了我所描述的问题,但遗憾的是并没有解决更大的问题。添加这个泛型类型意味着我现在不能将任一 Test 对象作为 ITest 来寻址,因为我现在需要指定泛型参数,这对于任一 Test 都不同。

解决方案(或至少 my 解决方案)是从ITest 接口中删除Actions 属性。 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.
    { /*...*/ }
}

【问题讨论】:

    标签: c# interface


    【解决方案1】:

    您可以通过在 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; }
    }
    

    【讨论】:

    • 但是当然你不能将ReadWriteTest 对象传递给期望ITest&lt;IAction&gt; 对象的方法。
    • @MatthewWatson 为什么不呢?
    • @MatthewWatson 也许我很困惑,但你是说void SomeFunc(ITest&lt;IAction&gt; input) 会与SomeFunc(new ReadWriteTest()); 冲突?
    • 对不起,我的意思是ReadTest:试试这个:首先,编译上面的代码示例(通过添加缺少的类型),然后添加这行代码:ITest&lt;IAction&gt; test = new ReadTest(); - 它会给编译错误,类似于“无法将类型 'ReadTest' 隐式转换为 'ITest'。存在显式转换(您是否缺少强制转换?)”(注意:它ReadWriteTest 因为它实现了ITest&lt;IAction&gt;,但ReadTest 没有。)
    • @Flater 为了比较,我将发布一个可能感兴趣的答案(尽管可能不是您想要的)。一分钟...
    【解决方案2】:

    如果您可以接受更改接口以使您无法直接通过属性设置操作,那么您可以使用out 关键字使类协变。

    这将允许您创建,例如,ReadWriteTest 类型的对象并将其传递给接受 ITest&lt;IAction&gt; 类型参数的方法。

    这是一个完整的可编译控制台应用程序来演示:

    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>();
    }
    

    【讨论】:

    • 如果我正确理解了您的(极好的!)答案,在泛型类型 T 上使用 out 关键字可以让 T 自动转换为其任何继承类型或实现的接口?另外,如果我不将set 添加到接口属性,我是否仍然可以实际将set 添加到实现接口的实际类中?如果我例如我的接口属性上只有一个get(例如string Name {get;}),我仍然可以在实际类上实现set(例如public string Name {get;set;}),或者在使用out的情况下这是不可能的关键字?
    • @Flater 答案是“kindof”。它实际上非常复杂,并且与您仅使用类型 T 作为输出这一事实有关。我无法在这里解释所有内容,但here's a Microsoft article about it.
    • 您基本上可以通过将out 添加到ITest、将List 更改为IEnumerable 并使属性只读来对我的答案执行相同的操作。
    猜你喜欢
    • 2012-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-04
    • 2011-05-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多