如何使用没有通配符的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
使用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
仅用于"输出"位置。