9

在 java 中,我非常习惯于使用泛型和通配符。像这样的事情:List<? extends Animal>。这允许您拥有 Animals 子类型的集合并在每个元素上运行通用例程(例如makeNoise())。我正在尝试在 C# 中完成此操作,但有点困惑,因为没有通配符。

在领域方面,我们在这里所做的是使用 SQL SMO 库从我们的数据库中收集脚本。我们有一个基本接口类型,它被多次扩展以编写脚本和收集不同的对象(表、视图、函数等——这是 T)

public interface IScripter<T> where T : IScriptable
{
    IList<T> CollectScripts(params...)
}

public abstract class AbstractScripter<T> : IScripter<T> where T : IScriptable
{
    ....
}

public class TableScripter : AbstractScripter<Table>
{
    ....
}

public class ViewScripter : AbstractScripter<View>
{
    ....
}

到现在为止还挺好。看起来像一个完全合理的对象层次结构对吗?这是我打算做的,直到我发现没有通配符:

public class Program
{
    static void Main(string[] args)
    {
        // auto discover all scripter modules, table, view, etc
        IList<Iscripter<? extends IScriptable>> allScripters = GetAllScripterModules(); 
        foreach (IScripter<? extends IScriptable> scripter in allScripters)
        {
            IList<? extends IScriptable> scriptedObjects = scripter.CollectScripts(...);
            // do something with scripted objects
        }
    }
 }

现在既然<? extends IScriptable>这里不存在,那我应该怎么做呢?我尝试了很多东西,泛型方法,只使用基本类型,各种讨厌的转换,但没有什么能真正奏效。

你会建议用什么来替换这IList<Iscripter<? extends IScriptable>件作品?

TIA

4

2 回答 2

12

通过使用outand 仅从接口中传递T或其他协变T接口,您可以使接口协变

public interface IScripter<out T> where T : IScriptable
{
    IEnumerable<T> CollectScripts(params...)
}

然后你不能添加到结果中,因为你不能使用非协变IList,所以当你想添加时添加单独的接口:

public interface IScripterAddable<T> where T : IScriptable
{
    //either:
    IList<T> CollectScripts(params...)
    //or add just what you need to, this is usually better
    //than exposing the underlying IList - basic encapsulation principles
    void AddScript(...)
}

然后只需删除? extends.

    // auto discover all scripter modules, table, view, etc
    IList<Iscripter<IScriptable>> allScripters = GetAllScripterModules(); 
    foreach (IScripter<IScriptable> scripter in allScripters)
    {
        IEnumerable<IScriptable> scriptedObjects = scripter.CollectScripts(...);
        // do something with scripted objects
    }
于 2013-03-27T15:42:27.443 回答
12

协方差是“如果一个苹果是水果,那么一碗苹果就是一碗水果”的性质。这立即提出了一个问题:您可以将橙子放入一碗水果中。如果一碗苹果就是一碗水果,你可以把一个橙子放进一碗水果里,那么你就可以把一个橙子放进一碗苹果里。那时显然它不再是一碗苹果。

C# 和 Java 采用两种不同的方法来防止这种违反类型安全的行为。C# 的方法是说协变接口必须预先声明其协变,并且该接口不公开任何可用于违反类型安全的方法。

因此IEnumerable<T>在 C# 中的 T 中是协变的,因为没有办法将橙子放入苹果序列中;上没有“添加”方法IEnumerable<T>。有一个 Add 方法IList<T>,因此它在 C# 中不是协变的。

Java 采用了不同的方法。它说“您现在可以将这碗苹果用作一碗水果,前提是您实际上并未在其中添加橙子。差异发生在特定位置,而不是界面的整体属性。

为了解决您的实际问题:如果您无法IScripter<T>在 T 中使您的接口协变,因为它可以交回一个IList<T>,您可能会被卡住。但是,如果你可以让它包含一个,IEnumerable<T>那么你可能会很幸运。将接口标记为IScripter<out T>,然后确保它T仅用于“输出”位置。

于 2013-03-27T16:40:47.153 回答