【发布时间】:2012-12-19 01:38:19
【问题描述】:
问题描述
我正在尝试存储通用 Foo<T> 元素的集合,其中每个项目的 T 可能不同。我也有像DoSomething<T>(Foo<T>) 这样的函数,它可以接受任何T 的Foo<T>。似乎我应该能够在上述列表的每个元素上调用这个函数,因为它们都是函数的有效参数,但我似乎无法向 C# 编译器表达这个想法。
据我所知,问题在于我不能真正表达这样的列表,因为 C# 不允许我在没有绑定 T 的情况下编写 Foo<T>。我想要的是类似于 Java 的通配符机制 (Foo<?>)。以下是它在 Pseudo-C# 中的外观,其中存在这种通配符类型:
class Foo<T> {
// ...
}
static class Functions {
public static void DoSomething<T>(Foo<T> foo) {
// ...
}
public static void DoSomething(List<Foo<?>> list) {
foreach(Foo<?> item in list)
DoSomething(item);
}
}
这种模式在 Java 中是有效的,但我怎样才能在 C# 中做同样的事情呢?我已经尝试了一些解决方案,我将在下面的答案中发布,但我觉得应该有更好的方法。
注意:我已经“足够好”地解决了这个问题以满足我的实际需要,并且我知道解决它的方法(例如使用dynamic 类型),但我真的很想看看是否有不放弃静态类型安全的更简单的解决方案。
仅使用object 或非泛型超类型(如下所示)不允许我调用需要Foo<T> 的函数。但是,即使我对T 一无所知,这也是明智的。例如,我可以使用Foo<T> 从某处检索List<T> list,从其他某处检索T value,然后调用list.Add(value),编译器将知道所有类型都正确。
动机
有人问我为什么我需要这样的东西,所以我正在制作一个更接近大多数开发人员日常体验的示例。想象一下,您正在编写一堆 UI 组件,这些组件允许用户操作某种类型的值:
public interface IUiComponent<T> {
T Value { get; set; }
}
public class TextBox : IUiComponent<string> {
public string Value { get; set; }
}
public class DatePicker : IUiComponent<DateTime> {
public DateTime Value { get; set; }
}
除了 Value 属性,组件当然还有许多其他成员(例如 OnChange 事件)。
现在让我们添加一个撤消系统。我们不应该为此修改 UI 元素本身,因为我们已经可以访问所有相关数据——只需连接 OnChange 事件,并且每当用户更改 UI 组件时,我们都会存储每个值IUiComponent<T>(有点浪费,但让我们保持简单)。为了存储值,我们将为表单中的每个IUiComponent<T> 使用Stack<T>。这些列表通过使用IUiComponent<T> 作为键来访问。我将省略列表存储方式的详细信息(如果您认为这很重要,我将提供一个实现)。
public class UndoEnabledForm {
public Stack<T> GetUndoStack<T>(IUiComponent<T> component) {
// Implementation left as an exercise to the reader :P
}
// Undo for ONE element. Note that this works and is typesafe,
// even though we don't know anything about T...
private void Undo<T>(IUiComponent<T> component) {
component.Value = GetHistory(component).Pop();
}
// ...but how do we implement undoing ALL components?
// Using Pseudo-C# once more:
public void Undo(List<IUiComponent<?>> components) {
foreach(IUiComponent<?> component in components)
Undo(component);
}
}
我们可以通过在所有IUiComponents(按名称)上直接调用Undo<T>() 来撤消所有操作:
public void Undo(List<IUiComponent<?>> components) {
Undo(m_TextBox);
Undo(m_DatePicker);
// ...
}
但是,我想避免这种情况,因为这意味着如果您添加/删除一个组件,您将不得不在代码中多修改一处。如果您想要对所有组件执行数十个字段和更多功能(例如,将它们的所有值写入数据库并再次检索它们),这可能会产生很多重复。
示例代码
这是一小段代码,可用于开发/检查解决方案。任务是将几个Pair<T>对象放入某种集合对象中,然后调用一个接受该集合对象的函数并交换每个Pair<T>的First和Second字段(使用Application.Swap()) .理想情况下,您不应使用任何强制转换或反射。如果您可以在不以任何方式修改Pair<T>-class 的情况下设法做到这一点,则可以加分:)
class Pair<T> {
public T First, Second;
public override string ToString() {
return String.Format("({0},{1})", First, Second);
}
}
static class Application {
static void Swap<T>(Pair<T> pair) {
T temp = pair.First;
pair.First = pair.Second;
pair.Second = temp;
}
static void Main() {
Pair<int> pair1 = new Pair<int> { First = 1, Second = 2 };
Pair<string> pair2 = new Pair<string> { First = "first", Second = "second" };
// imagine more pairs here
// Silly solution
Swap(pair1);
Swap(pair2);
// Check result
Console.WriteLine(pair1);
Console.WriteLine(pair2);
Console.ReadLine();
}
}
【问题讨论】:
-
不使用泛型怎么样?看起来你想要一个对象的集合。
-
也许您可以详细说明为什么要将
Foo设为泛型?Foo<?>本质上是动态的,那么为什么要使用泛型呢?
标签: c# generics collections type-systems