如何使用没有通配符的c#泛型

本文关键字:泛型 通配符 何使用 | 更新日期: 2023-09-27 18:06:03

在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>
{
    ....
}

到目前为止一切顺利。似乎是一个非常合理的对象层次结构,对吧?下面是我打算做的,直到我发现没有通配符:

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

如何使用没有通配符的c#泛型

使用out,只将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
    }

协方差是"如果苹果是一种水果,那么一碗苹果就是一碗水果"的属性。这马上就出现了一个问题:你可以把一个橙子放进一碗水果里。如果一碗苹果就是一碗水果,你可以把一个橙子放进一碗水果里,那么你也可以把一个橙子放进一碗苹果里。这时显然不再是一碗苹果了。

c#和Java采用两种不同的方法来防止违反类型安全。c#的方法是,协变接口必须预先声明其协方差,并且接口不暴露任何可能违反类型安全的方法。

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

Java采用了不同的方法。它说"你现在可以把这碗苹果当作一碗水果,前提是你没有在里面添加一个橙子。"差异发生在特定的位置,而不是界面的整体属性。

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