提问人:sbi 提问时间:6/19/2010 最后编辑:George Stockersbi 更新时间:6/21/2010 访问量:644
使用 .NET 泛型改进具有可变参数的方法
Improving methods with variable parameters using .NET Generics
问:
我有很多功能目前超载运行,并且:int
string
bool foo(int);
bool foo(string);
bool bar(int);
bool bar(string);
void baz(int p);
void baz(string p);
然后,我有很多函数接受 或 的 1、2、3 或 4 个参数,它们调用上述函数:int
string
void g(int p1) { if(foo(p1)) baz(p1); }
void g(string p1) { if(foo(p1)) baz(p1); }
void g(int p2, int p2) { if(foo(p1)) baz(p1); if(bar(p2)) baz(p2); }
void g(int p2, string p2) { if(foo(p1)) baz(p1); if(bar(p2)) baz(p2); }
void g(string p2, int p2) { if(foo(p1)) baz(p1); if(bar(p2)) baz(p2); }
void g(string p2, string p2) { if(foo(p1)) baz(p1); if(bar(p2)) baz(p2); }
// etc.
注意:该系列的实现只是一个示例g()
比当前或可能随时引入的类型更多。参数多于 4 的函数也是如此。当前相同功能的数量几乎无法管理。在任一维度上再添加一个变体,组合爆炸将非常巨大,它可能会吹走应用程序。int
string
在 C++ 中,我会模板化并完成。g()
我知道 .NET 泛型是不同的。我已经与他们战斗了两个小时,试图想出一个不涉及复制和粘贴代码的解决方案,但无济于事。
C# 泛型是否要求我为一系列函数键入相同的代码,这些函数采用三种类型中的任何一种的五个参数?
我错过了什么?
编辑:这些函数用于解析来自某个来源的一堆参数(当前是 或 )。想象一下,并且能够同时读取 or ,以及指定要解析的参数的类型和数量的族(隐式地,通过其参数的类型)。int
string
bar()
baz()
int
string
g()
答:
不幸的是,泛型无法处理这种情况。至少,不是很好。如果将方法设置为泛型,则几乎可以将任何类型传递给它们。泛型上没有足够的 where 子句来限制它只能是 string 和 int。如果你的方法要在其中执行特定的 int/string 相关操作,那么泛型根本不起作用。
C# 中的泛型不如 C++ 中的模板强大,是的,它们有时会引起一些重大麻烦。只是需要时间来适应它们并感受它们能做什么和不能做什么。
评论
int
string
double
你在这里真正的问题很可能是设计问题,而不是泛型可以用来做什么。泛型应该用于实际上与类型无关的事情,而不是作为让生活更轻松的包罗万象。也许尝试发布一些您正在使用的实际示例代码,并且有人可能会对如何重新设计您的解决方案有所了解,以便您能够扩展它而不会那么麻烦。
作为预告片,请考虑如下内容:
public void DoSomethingConditionally<T>(T key, Func<T, bool> BooleanCheck, Action<T> WhatToDo)
{
if (BooleanCheck(key)) WhatToDo(key);
}
你可以这样称呼它:
DoSomethingConditionally<String>("input", v => v == "hello", s => Console.WriteLine(s));
我在这里使用了 lambda 表达式,但您也可以轻松地预定义一些执行一些常见表达式的 s。这将比方法重载好得多,并且会强制您在设计时处理新的输入类型。Func<>
评论
g()
int
string
g()
int
string
ref
out
g()
g()
DoSomethingConditionally()
如果您使用的是 c# 4.0,则可以使用 option 参数执行此操作 或者你可以使用 object
Foo(object o)
{
if (o is int){ }
else if (o is string){ }
}
或者您可以使用泛型方法Foo<T>(T o){ }
评论
where
使用对象列表。
如果在计划时参数数量未知,只需使用对象列表即可。像这样:
void g(params object[] args) {
foreach (object arg in args) {
if ((arg is int) && (foo((int)arg))) baz((int)arg) else
if ((arg is string) && (foo((string)arg))) baz((string)arg)
}
}
(假设你有,...bool foo(int)
bool foo(string)
因此,您可以调用:
g(p1, p2);
g(p1);
g(p1, p2, p3)
使用类型的任意组合,因为每个引用都派生自 object(它可能比所需的类型多得多,int 和 string,但将来可以方便地支持更多其他类型)。
这是可能的,因为您可以在运行时使用 Reflection 来识别类型。
执行一系列操作的另一种方法是使用接口,定义在特定条件下对特定对象执行的操作。
interface IUpdatable {
void Update(object[] data);
}
class object1 : IUpdatable { public void Update(object data) { baz(data); } }
class object2 : IUpdatable { public void Update(object data) { baz(data); } }
void g(params IUpdatable[] args) {
foreach (IUpdatable arg in args) {
arg.Update(args);
}
}
但是这样一来,你必须将 p1 和 p2(以及 p3)建模为实现接口的对象,这是不可能的。
评论
在这种情况下,请考虑使用继承。我假设 ,并且是类型固有的(在您的情况下为 int 或 string)。如果这不是真的,请更正或评论此答案。foo
bar
baz
using System;
namespace ConsoleApplication3
{
abstract class Param
{
public abstract bool Foo();
public abstract bool Bar();
public abstract void Baz();
public static IntParam Create(int value)
{
return new IntParam(value);
}
public static StringParam Create(string value)
{
return new StringParam(value);
}
}
abstract class Param<T> : Param {
private T value;
protected Param() { }
protected Param(T value) { this.value = value; }
public T Value {
get { return this.value; }
set { this.value = value; }
}
}
class IntParam : Param<int>
{
public IntParam() { }
public IntParam(int value) : base(value) { }
public override bool Foo() { return true; }
public override bool Bar() { return true; }
public override void Baz()
{
Console.WriteLine("int param value is " + this.Value);
}
}
class StringParam : Param<string>
{
public StringParam() { }
public StringParam(string value) : base(value) { }
public override bool Foo() { return true; }
public override bool Bar() { return true; }
public override void Baz()
{
Console.WriteLine("String param value is " + this.Value);
}
}
class Program
{
static void g(Param p1)
{
if (p1.Foo()) { p1.Baz(); }
}
static void g(Param p1, Param p2)
{
if (p1.Foo()) { p1.Baz(); }
if (p2.Bar()) { p2.Baz(); }
}
static void Main(string[] args)
{
Param p1 = Param.Create(12);
Param p2 = Param.Create("viva");
g(p1);
g(p2);
g(p1, p1);
g(p1, p2);
g(p2, p1);
g(p2, p2);
Console.ReadKey();
}
}
}
这将输出:
int param value is 12
String param value is viva
int param value is 12
int param value is 12
int param value is 12
String param value is viva
String param value is viva
int param value is 12
String param value is viva
String param value is viva
对于新的受支持类型,您可以:
- 创建一个支持该类型和扩展的新类
Param<T>
; - 实现 ,并针对该新类型;
Foo
Bar
Baz
- 创建一个具有另一个参数的新方法(只有一个)。
g
特别是对于3)这将大大减少方法的爆炸。现在,您可以为任意给定数量的参数编写单个方法。在以前的设计中,您必须为参数编写方法(n = 1 -> 2 个方法,n = 2 -> 4 个方法,n = 3 > 8 个方法,..)。g
n
2^n
评论
g()
<sigh>
没有我想要的那么理想......但是,如果 、 和 也有通用版本呢?foo
bar
baz
static bool foo(int input)
{
return input > 5;
}
static bool foo(string input)
{
return input.Length > 5;
}
static void baz(int input)
{
Console.WriteLine(input);
}
static void baz(string input)
{
Console.WriteLine(input);
}
static bool foo<T>(T input)
{
if (input is int) return foo((int)(object)input);
if (input is string) return foo((string)(object)input);
return false;
}
static void baz<T>(T input)
{
if (input is int) baz((int)(object)input);
else if (input is string) baz((string)(object)input);
else throw new NotImplementedException();
}
static void g<T>(T input)
{
if (foo(input))
baz(input);
}
static void g<T, U>(T input, U inputU)
{
g(input);
g(inputU);
}
评论
foo()
, and
int
string
object
<sigh>
这可能有点繁重,但是将不同的参数类型封装为类是否有效?
public abstract class BaseStuff
{
public abstract bool Foo();
public abstract bool Bar();
public abstract void Baz();
public void FooBaz()
{
if(Foo()) Baz();
}
public void BarBaz()
{
if(Bar()) Baz();
}
}
public class IntStuff : BaseStuff
{
private int input;
public IntStuff(int input)
{
this.input = input;
}
public bool Foo()
{
//logic using input for example
return input > 0;
}
//implement Bar and Baz using input
}
public class StringStuff : BaseStuff
{
private string input;
public IntStuff(string input)
{
this.input = input;
}
//Implement Foo, Bar and Baz
}
然后在某处有一些 G 方法:
public void G(BaseStuff stuff1)
{
stuff1.FooBaz();
}
public void G(BaseStuff stuff1, BaseStuff stuff2)
{
stuff1.FooBaz();
stuff2.BarBaz();
}
然后,您可以致电:
G(new IntStuff(10), new StringStuff("hello"));
G(new StringStuff("hello"), new StringStuff("world"));
评论
如果您使用的是 C#/.NET 4.0,则可以使用动态功能实现多重调度,因此只需根据参数数量实现单个 g 重载,并且每个 g 实现中按类型划分的正确 foo/bar/baz 重载将在运行时解析。
void g(dynamic p1) { if (foo(p1)) baz(p1); }
void g(dynamic p1, dynamic p2) { if (foo(p1)) baz(p1); if (bar(p2)) baz(p2); }
编辑:
即使您无法使用 C#/.NET 4.0,您仍然可以通过反射使用此方法。我为 double 添加了另一个 foo/bar/baz 重载,显示了它如何概括并允许您消除重复的 g 实现。
bool foo(int p) {Console.WriteLine("foo(int)=" + p); return p == 0;}
bool foo(string p) {Console.WriteLine("foo(string)=" + p); return p == "";}
bool foo(double p) { Console.WriteLine("foo(double)=" + p); return p == 0.0; }
bool bar(int p) {Console.WriteLine("bar(int)=" + p); return p == 1;}
bool bar(string p) { Console.WriteLine("bar(string)=" + p); return p == ""; }
bool bar(double p) { Console.WriteLine("bar(double)=" + p); return p == 1.1; }
void baz(int p) {Console.WriteLine("baz(int)=" + p);}
void baz(string p) { Console.WriteLine("baz(string)=" + p); }
void baz(double p) { Console.WriteLine("baz(double)=" + p); }
//these object overloads of foo/bar/baz allow runtime overload resolution
bool foo(object p)
{
if(p == null) //we need the type info from an instance
throw new ArgumentNullException();
//may memoize MethodInfo by type of p
MethodInfo mi = typeof(Program).GetMethod(
"foo",
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
null,
new Type[] { p.GetType() },
null
);
if (mi.GetParameters()[0].ParameterType == typeof(object))
throw new ArgumentException("No non-object overload found");
return (bool)mi.Invoke(this, new object[] { p });
}
bool bar(object p)
{
if (p == null)
throw new ArgumentNullException();
MethodInfo mi = typeof(Program).GetMethod(
"bar",
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
null,
new Type[] { p.GetType() },
null
);
if (mi.GetParameters()[0].ParameterType == typeof(object))
throw new ArgumentException("No non-object overload found");
return (bool)mi.Invoke(this, new object[] { p });
}
void baz(object p)
{
if (p == null)
throw new ArgumentNullException();
MethodInfo mi = typeof(Program).GetMethod(
"baz",
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
null,
new Type[] { p.GetType() },
null
);
if (mi.GetParameters()[0].ParameterType == typeof(object))
throw new ArgumentException("No non-object overload found");
mi.Invoke(this, new object[] { p });
}
//now you don't need to enumerate your identical implementations of g by type
void g(object p1) { if (foo(p1)) baz(p1); }
void g(object p1, object p2) { if (foo(p1)) baz(p1); if (bar(p2)) baz(p2); }
评论
您可以使用代码生成来解决此问题。
查看 Reflection.Emit。还可以在 Visual Studio 中使用 T4 生成代码。
这些类型确实阻碍了这里。您也可以尝试使用动态语言或 C# 4 dynamic 关键字来解决此问题。
我本来会这样做作为对@smink的评论,但我没有足够的代表......
如果扩展 Param 基类以使用隐式运算符,则不必将内容包装在代码中(尽管运行时仍然会产生包装开销)...
abstract class Param
{
...
public static implicit operator Param(int value)
{ return new IntParam(value); }
}
评论