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.
    { /*...*/ }
}

      

+3


source to share


2 answers


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

      

+2


source


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

      

+6


source







All Articles