2014-09-26 27 views
3

我在描述到的問題在標題一時間難以超越的接口特性,對不起,如果標題不清晰的。如何具有遺傳型

假設我有以下接口/類:現在

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屬性。

有沒有辦法來限制此的ActionList在ReadTest類定義的內容?

我可以通過創建一個自定義的AddAction(IAction action)方法來解決這個問題,該方法根本不會添加任何WriteAction對象,但我希望得到更加優雅的解決方案來解決這個問題。

這可能嗎?對最終結果

作爲回答

更新,它可以通過添加一個泛型類型的接口來完成。這解決了我所描述的問題,但遺憾的是並沒有解決更大的問題。添加此generict類型意味着我現在無法將任何Test對象作爲ITest,因爲我現在需要指定通用參數,這對於任一Test都是不同的。

該解決方案(或至少,我的解決方案)是從ITest接口刪除Actions財產。 Actions屬性仍然存在於類中。
我在界面中添加了一個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. 
    { /*...*/ } 
} 

回答

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

如果我正確理解你的(超級!)答案,在泛型類型T上使用'out'關鍵字允許T自動轉換爲它的任何繼承類型或實現的接口?另外,如果我不向接口屬性添加'set',我是否仍然可以實際添加一個'set'到實現接口的實際類?如果我例如在我的接口屬性(例如'string Name {get;}')上只有'get',我仍然可以在實際的類上實現一個'set'(例如'public string Name {get; set;}'),或者這在使用'out'關鍵字的情況下是不可能的? – Flater 2014-09-26 11:49:30

+0

忽略我的第二個問題,我忘了'readonly' :) – Flater 2014-09-26 11:53:37

+0

@Flater答案是「kindof」。這實際上相當複雜,並且與您只使用類型T作爲輸出的事實有關。我無法在這裏解釋這一切,但[這裏是關於它的微軟文章](http://msdn.microsoft.com/en-us/library/dd799517.aspx)。 – 2014-09-26 12:00:38

6

您可以通過使用通用型解決這個問題在你的ITest interfa ce:

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

不過,當然,你不能傳遞'ReadWriteTest'對象以期待的'ITEST '對象的方法。 – 2014-09-26 10:53:51

+0

@MthetheWWatson爲什麼不呢? – DavidG 2014-09-26 10:56:44

+0

因爲編譯器會給你一個錯誤。 – 2014-09-26 10:58:43