问题描述
我正在尝试存储通用Foo<T>
元素的集合,T
每个项目的位置可能不同。我也有类似DoSomething<T>(Foo<T>)
的功能可以接受Foo<T>
任何T
. 似乎我应该能够在上述列表的每个元素上调用这个函数,因为它们都是函数的有效参数,但我似乎无法向 C# 编译器表达这个想法。
据我所知,问题在于我不能真正表达这样的列表,因为 C# 不允许我在Foo<T>
没有绑定的情况下编写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
or 非泛型超类型不允许我调用需要Foo<T>
. 但是,即使我对T
. 例如,我可以使用来从某处Foo<T>
检索 a ,从其他地方检索 a ,然后调用,编译器将知道所有类型都正确。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>
(有点浪费,但让我们保持简单)。为了存储值,我们将在表单中使用Stack<T>
for each IUiComponent<T>
。使用IUiComponent<T>
as 键可以访问这些列表。我将省略列表存储方式的详细信息(如果您认为这很重要,我将提供一个实现)。
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);
}
}
我们可以通过直接调用Undo<T>()
所有IUiComponent
s(按名称)来撤消所有操作:
public void Undo(List<IUiComponent<?>> components) {
Undo(m_TextBox);
Undo(m_DatePicker);
// ...
}
但是,我想避免这种情况,因为这意味着如果您添加/删除一个组件,您将不得不在代码中多接触一个地方。如果您想要对所有组件执行数十个字段和更多功能(例如,将它们的所有值写入数据库并再次检索它们),这可能会产生大量重复。
示例代码
这是一小段代码,可用于开发/检查解决方案。任务是将多个Pair<T>
- 对象放入某种集合对象中,然后调用一个函数,该函数接受该集合对象并交换每个对象的First
andSecond
字段Pair<T>
(使用Application.Swap()
)。理想情况下,您不应使用任何强制转换或反射。如果您可以在不以任何方式修改 -class 的情况下设法做到这一点,则可以加分Pair<T>
:)
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();
}
}