如何在没有通配符的情况下使用C#泛型?

Joh*_*man 9 c# generics wildcard covariance

在java中,我非常习惯使用泛型和通配符.比如:List<? extends Animal>.这允许您拥有一组动物的子类型,并在每个元素上运行通用例程(例如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>
{
    ....
}
Run Code Online (Sandbox Code Playgroud)

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

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
        }
    }
 }
Run Code Online (Sandbox Code Playgroud)

既然<? extends IScriptable>这里不存在,我应该做什么呢?我已经尝试了很多东西,通用方法,只是使用基类型,各种讨厌的铸造,但没有真正的诀窍.

你有什么建议更换这IList<Iscripter<? extends IScriptable>件作品?

TIA

wes*_*ton 11

通过使用out并且T仅从T接口传递或其他共存接口,可以使接口协变 ;

public interface IScripter<out T> where T : IScriptable
{
    IEnumerable<T> CollectScripts(params...)
}
Run Code Online (Sandbox Code Playgroud)

然后你无法添加到结果中,因为你不能使用not covarient,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(...)
}
Run Code Online (Sandbox Code Playgroud)

然后删除? 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
    }
Run Code Online (Sandbox Code Playgroud)


Eri*_*ert 11

协方差是"如果苹果是水果,那么一碗苹果就是一碗水果"的属性.这立刻就出现了问题:你可以将橙子放入一碗水果中.如果一碗苹果是一碗水果,你可以将橙子放入一碗水果中,那么你可以将橙子放入一碗苹果中.那时显然它不再是一碗苹果.

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仅用于"输出"位置.

  • 迄今为止我看到的最好和最清晰的解释!自从从 Java 转为 C# 以来,我真的一直在思考这个问题,但我想我现在终于明白如何正确使用它了。谢谢你!!! (2认同)