[英]C# advanced constrains on generic types
我正在寻找一种解决句法问题的好方法。 我想实现一个导航系统,允许在不同的视图之间切换。 下一个视图应由其类型TView指定(而不是该类型的实例)。 然后,将使用一些通用参数来初始化视图
public abstract class ViewBase { }
public abstract class ViewBase<T0, T1> : ViewBase
{
public abstract void Initialize(T0 arg0, T1 arg1);
}
public static class Navigator
{
public static void Navigate<TView, T0, T1>(T0 arg0, T1 arg1) where TView : ViewBase<T0, T1>
{
var view = (ViewBase<T0, T1>)Activator.CreateInstance<TView>();
view.Initialize(arg0, arg1);
/* ... */
}
}
上面的代码应该可以工作,但是每次调用Navigate时,我都不喜欢一长串的泛型类型:
Navigate<DerivedView, string, int>("Joe", 42);
假设派生类:
public class DerivedView : ViewBase<string, int>
{
/* ... */
}
一般而言,可以将其简化为:
Navigate<DerivedView>("Joe", 42);
因为类型信息是多余的。
你有什么建议吗? 我缺少可变参数模板;)
谢谢
类型推断是全有还是全无。 如果指定了任何类型参数,则必须全部指定它们。 因此,您需要一种方法来执行以下任一操作:
TView
TView
作为类型参数 T0
和T1
作为类型参数 您可以允许TView
由一个工厂函数传递给指定推断TView
的类型:
public static void Navigate<TView, T0, T1>(Func<TView> factory, T0 arg0, T1 arg1)
where TView : ViewBase<T0, T1> {
var view = factory();
view.Initialize(arg0, arg1);
// ...
}
现在,代替Navigate<DerivedView>("Joe", 42)
,调用Navigate(() => new DerivedView(), "Joe", 42)
。
注意:这需要arg0
和arg1
的类型与TView
指定的完全匹配。 隐式转换将不起作用。 如果视图是从ViewBase<int, object>
派生的,而arg1
是"Hello"
,则将T1
推断为string
类型,从而导致编译器错误消息。
由于除了构造实例之外,避免使用TView
的类型,因此也可以通过使用工厂函数来避免将TView
作为类型参数:
public static void Navigate<T0, T1>(Func<ViewBase<T0, T1>> factory, T0 arg0, T1 arg1) {
var view = factory();
view.Initialize(arg0, arg1);
// ...
}
由于T0
和T1
都可以从factory
推导,因此这也避免了隐式转换的问题。
至于避免将T0
和T1
作为类型参数:名称Initialize
暗示其功能可能属于构造函数。 在这种情况下,您可以通过将构造留给调用者来避免使用它们作为类型参数:factory函数可以是() => new DerivedView("Joe", 42)
。 但是,如果不是这种情况,那么如果您确实需要由Navigate
调用的单独的Initialize
方法,那么我看不到任何避免将T0
和T1
作为类型参数的选项。
最后,拆分类型参数:
public static class Navigator {
public static WithViewHelper<TView> WithView<TView>()
where TView : new() => new WithViewHelper<TView>();
public struct WithViewHelper<TView> where TView : new() { }
}
public static class NavigatorExtensions {
public static void Navigate<TView, T0, T1>(this Navigator.WithViewHelper<TView> withViewHelper, T0 arg0, T1 arg1)
where TView : ViewBase<T0, T1>, new() {
var view = new TView();
view.Initialize(arg0, arg1);
}
}
这使您可以调用Navigator.WithView<DerivedView>().Navigate("Joe", 42)
。 它需要Navigate
作为扩展方法,因为否则无法表达泛型类型约束。 隐式转换又遇到了问题: Navigator.WithView<DerivedView>().Navigate(null, 42)
将无法编译:即使null
可转换为string
,编译器也不会确定T0
应该是string
。
我想您想要的是一个约束,可以归结为:
class Generic<T> where T : Generic2<T1, T2>
并且可以在您的泛型成员签名中访问T1,T2。
不幸的是,C#当前不支持此功能。
除了提出的方法外,我还想出了从ViewBase类创建对象的方法:
public abstract class ViewBase<T0, T1>
{
public abstract void Initialize(T0 arg0, T1 arg1);
public static T Navigate<T>(T0 arg0, T1 arg1) where T : ViewBase<T0, T1>
{
var view = Activator.CreateInstance<T>();
view.Initialize(arg0, arg1);
return view;
}
}
这允许以下调用:
DerivedView.Navigate<DerivedView>("Joe", 42);
但这也带有冗余代码..或:
ViewBase.Navigate<DerivedView>("Joe", 42);
还考虑使用通用工厂。 我缓存了构造函数调用签名,以便第二次更快地进行检索。
public static class Factory
{
//store constructors for type T
static Dictionary<string, ConstructorInfo> ctors=new Dictionary<string, ConstructorInfo>();
public static T New<T>(params object[] args)
{
var arg_types=args.Select((obj) => obj.GetType());
var arg_types_names=args.Select((obj) => obj.GetType().Name);
string key=string.Format("{0}({1})", typeof(T).Name, string.Join(",", arg_types_names);
if(!ctors.ContainsKey(key))
{
// if constructor not found yet, assign it
var ctor=typeof(T).GetConstructor(arg_types.ToArray());
if(ctor==null)
{
throw new MissingMethodException("Could not find appropriate constructor");
}
ctors[key]=ctor;
}
// invoke constructor to create a new T
return (T)ctors[key].Invoke(args);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.