我有点问题。
下面是一个代码示例。如何使用testVar
这样的变量可以ITest<T>
?显然我得到了一个施法错误。有没有什么方法可以完全使用通用接口而不进行转换?
我尝试T
协变(out T)
,但由于ResultEventArgs
,这是不可能的。
using System;
namespace InterfaceGenericFunApp
{
public class ResultEventArgs<T> : EventArgs
{
public T Result { get; set; }
}
public interface ITest<T>
{
event EventHandler<ResultEventArgs<T>> ResultReceived;
}
public class TestClass : ITest<string>
{
public event EventHandler<ResultEventArgs<string>> ResultReceived;
}
public class Program
{
private static ITest<object> testVar;
public static void Main(string[] args)
{
testVar = new TestClass();
}
}
}
答案 0 :(得分:3)
没有简单的方法可以让它发挥作用。
你的变量testVar
承诺我得到一个具有事件arg的事件的类,我可以将Result
设置为任何对象。
您想要分配的是一个类,最后我只能将string
分配给Result
属性。对于能够设置任何对象的上述承诺,这不起作用。
如果T
的所有部分都是只读的,您可以解决此问题,因为读取对象可以读取字符串。但写作是有问题的。
答案 1 :(得分:1)
看看这里:
public class Program
{
private static ITest<string> testVar;
public static void Main(string[] args)
{
testVar = new TestClass();
}
}
您不能拥有ITest<object>
,因为string
是object
的规范。您必须将ITest
设置为string
。
编辑: 您还可以使TestClass通用:
public class TestClass<T> : ITest<T>
{
public event EventHandler<ResultEventArgs<T>> ResultReceived;
}
public class Program
{
private static ITest<string> testVar;
public static void Main(string[] args)
{
testVar = new TestClass<string>();
}
}
答案 2 :(得分:1)
没有
C#泛型介于Java的泛型和C ++模板之间。在Java中,这很容易 - 您只需使用ITest<?>
。在C#和C ++中,这是不可能的。
虽然Java的泛型仅在编译时存在(基本上,为了允许额外的静态类型检查),C#是真实的&#34;在运行时也是如此。例如,想象一个简单的泛型类,除了另一个值的包装之外什么都不是:
class Test<T>
{
public T Value;
}
在Java中,该字段的类型始终为Object
- Test<?>
,Test<String>
和Test<Integer>
在运行时完全相同。
在C#中,该字段将完全泛型类型参数中指定的类型。这是巨大的差异 - 例如,这意味着如果您使用值类型,则不会将其装箱。这也意味着Test<?>
没有任何意义 - 泛型类型的不同实例可以具有完全不同的内存布局。这也意味着只允许通常的方差:子类型可以分配给父类型,反之则不然 - 可以传递子类型而不是父类型,反之则不然。
这解释了您使界面协变的麻烦 - 简单地说,它不是协变。 EventHandler
传递您的值而不是返回它,因此您只能传递子类型,而不是父类型(例如,传递string
而不是object
object
,但不是string
而不是ITest<string>
)。唯一可能的差异是逆变 - 您可以使用ITest<object>
来存储Func<T>
的实例。 Action<T>
是协变的,而Func<T, U>
是逆变的。当然,T
相对于U
和ITest<Whatever>
的协变而言是逆变的。
如何解决实际问题完全取决于您的问题 。例如,如果您只构建对象集合,则可以使用非通用接口来公开您需要的任何内容。您也可以手动将值转换为foreach (var component in Components.OfType<ITest<Whatever>>()) ...
- 尽管这显然意味着在某种程度上失去了类型安全性。或者,如果您只需要在任何时候使用特定的实例化,您可以单独使用每种类型:
ShowDialog
修改强>
在你的情况下,似乎你想根据一些不是模态的对话框的结果做一些动作,否则你只需使用ShowDialogAsync
并同步完成,对吧?
当然,这需要某种形式的异步性。最简单的是一个简单的委托 - 让我们假设你在每个单独的对话框上都有一个方法public static void ShowDialogAsync(Action<string> action) { ... }
,接受一个函数,该函数在确认时接受对话框提供的参数。例如,返回单个字符串的对话框可以使用如下方法:
UsernameDialog.ShowDialogAsync(i => Console.WriteLine(i));
这里的关键是对话框知道你提供的确切代表类型,调用者也可以这样做 - 不需要存储&#34;全局&#34;在任何地方。
电话会看起来像这样:
private readonly Action<string> _action;
private UsernameDialog(Action<string> action)
{
_action = action;
}
public static void ShowDialogAsync(Action<string> action)
{
var dialog = new UsernameDialog(action);
dialog.Show();
}
// I wouldn't actually bind this to a button click, this is just an example
public void btnOk_Click(object sender, EventArgs e)
{
_action(tbxUsername.Text);
Close();
}
因此,每当对话框完成时,将使用从对话框的文本框中获取的参数调用强类型委托(例如)。整个对话框看起来像这样:
Task
如果您之前见过await
或ShowDialogAsync
,那么此处的模式应该是显而易见的 - 实际上,让Task<string>
返回Action<string>
是微不足道的而不是接受var username = await UsernameDialog.ShowDialogAsync();
,允许你这样做:
UsernameDialog.ShowDialogAsync().ContinueWith(t => Console.WriteLine(t.Result));
事实上,
Task<T>
这两种方法非常对称 - 主要区别在于思维; Action<T>
&#34;拉&#34;结果,而ContinueWith
&#34;推动&#34;结果:虽然很明显,任何一方都可以用来做相反的事情;在Task<T>
示例中,我已使用Action<T>
推送值。从string result = null;
UsernameDialog.ShowDialogAsync(i => { result = i; });
中提取值也非常简单:
Action<T>
虽然应该注意,只有ShowDialogAsync
在Task<T>
退出之前实际执行时才有意义 - Task<T>.Result
通过在您使用{{1}时等待结果来处理此问题};你可以通过使用类似的东西对动作做同样的事情:
string result = null;
var waitHandle = new ManualResetEventSlim();
UsernameDialog.ShowDialogAsync(i => { result = i; waitHandle.Set(); });
waitHandle.Wait();
显然,这是一个人为的例子 - 使用异步回调只是为了强制它们是同步的 - 但它应该说明对称性。在现实世界中,您只需返回Task<T>
,或使用异步回调。
这四个示例中的关键点是,从不需要任何全局状态来处理对话 - 无论是同步还是异步处理它。这使您可以在实际需要时始终使用已知类型 - 当您显示对话框时,它是已知对话框,并且参数已知;当你处理回调(或承诺)时,你再次在实际知道它意味着什么的代码片段中这样做。
我们假设您需要显示一个对话框,允许您更改另一种形式的用户名 - 即使使用手动回调,这也很简单
UsernameDialog.ShowDialogAsync(userName => { lblUsername.Text = username; });
这里的诀窍是使用闭包 - 基本上,lblUsername
(更准确地说,this
,即表格)是默默地&#34;传递&#34;以及回调,而不必是Action
委托的明确参数!
这是完全一般的,并且允许您使用函数编写任何您想要的东西(严肃地说,我们在这里谈论数学定理 - 任何命令式程序都可以转换为功能性程序 - 代表可以更多(或者少于)功能)。
我坚持回归Task<...>
如果它有意义 - 回调很好,但很容易在回调地狱中结束。 <{1}}与Task<...>
一起使用很多更容易。