I have a lot of functions which are currently overloaded to operate on int
and string
:
bool foo(int);
bool foo(string);
bool bar(int);
bool bar(string);
void baz(int p);
void baz(string p);
I then have a lot of functions taking 1, 2, 3, or 4 arguments of either int
or string
, which call the aforementioned functions:
void g(int p1) { if(foo(p1)) baz(p1); }
void g(string p1) { if(foo(p1)) baz(p开发者_JAVA技巧1); }
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.
Note: The implementation of the g()
family is just an example
More types than the current int
or string
might be introduced at any time. The same goes for functions with more arguments than 4. The current number of identical functions is barely manageable. Add one more variant in either dimension and the combinatoric explosion will be so huge, it might blow away the application.
In C++, I'd templatize g()
and be done.
I understand that .NET generics are different. I have been fighting them for two hours now trying to come up with a solution that doesn't involve copy and pasting code, to no avail.
C# generics won't require me to type out identical code for a family of functions taking five arguments of either of three types?
What am I missing?
Edit: These functions are used to parse a bunch of arguments (currently either int
or string
) from some source. Imagine bar()
and baz()
being able to read both int
or string
, and the g()
family specifying the type and number of arguments to parse (implicitly, by their arguments' types).
Consider using inheritance for this case. I am assuming that foo
, bar
and baz
are inherent to the type (int or string in your case). If this is not true please correct or comment this answer.
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();
}
}
}
This would output:
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
For a new supported type you:
- create a new class that supports the type and extends
Param<T>
; - implement
Foo
,Bar
andBaz
for that new type; - Create a new
g
method (just one) that has another parameter.
Specially for 3) this would greatly reduce explosion of methods. Now you write a single g
method for any given number of parameters. With previous design you had to write, for n
parameters, 2^n
methods (n = 1 -> 2 methods, n = 2 -> 4 methods, n = 3 -> 8 methods, ..).
Your true problem here is most likely one of design, rather than something generics can be used for. Generics should be used for things that are actually type-agnostic, not as a catch-all to make life a bit easier. Perhaps try posting some actual example code you're using, and someone may have an idea about how to redesign your solution in a way that will allow you to extend it without so much headache.
As a teaser, consider something like this:
public void DoSomethingConditionally<T>(T key, Func<T, bool> BooleanCheck, Action<T> WhatToDo)
{
if (BooleanCheck(key)) WhatToDo(key);
}
And you could call it like this:
DoSomethingConditionally<String>("input", v => v == "hello", s => Console.WriteLine(s));
I've used lambda expressions here, but you could just as easily predefine a few Func<>
s that perform some common expressions. This would be a much better pattern than method overloading, and would force you to handle new input types at design time.
Not as optimal as I would like... but what if foo
, bar
, and baz
had generic versions as well?
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);
}
Use a list of objects.
In the case the number of parameters are unknown at planning time, just use a list of objects. Something like:
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)
}
}
(Assuming you have bool foo(int)
, bool foo(string)
...)
So you can call:
g(p1, p2);
g(p1);
g(p1, p2, p3)
with any combination of the types, since every reference derives from object (which it could be many more types than required, int and string, but could be handy in future to support more other types).
This is possible since you could use Reflection to recognize the type at runtime.
Another way to execute a sequence of operation is the use of interfaces, defining the action to execute at certain conditions, on certain objects.
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);
}
}
But this way you have to model p1 and p2 (but also p3, as objects implementing an interface, which could be not possible.
I would have done this as a comment to @smink, but I don't have enough rep...
If you extend the Param base class to have implicit operators you are back to not having to wrap the contents in code (though the runtime still incurs the wrapping overhead)...
abstract class Param
{
...
public static implicit operator Param(int value)
{ return new IntParam(value); }
}
If you are using C# / .NET 4.0, you can achieve multiple dispatch using the dynamic feature so you only have to implement a single overload of g based on the number of arguments and the proper foo/bar/baz overloads by type inside each g implementation will be resolved at runtime.
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); }
Edit:
Even though you are unable to use C# / .NET 4.0, you can still use this approach using reflection. I've added another foo/bar/baz overload for double showing how well this generalizes and allows you to eliminate duplicate g implementations.
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); }
Unfortunately, generics cannot handle this situation. At least, not well. If you make your methods generic, then just about any type can be passed into them. There won't be an adequate where clause on the generics to limit it to just string and int. If your methods are going to do specific int/string related operations inside them, then generics wont work at all.
Generics in C# are not nearly as powerful as templates in C++, and yes they can cause some major headaches at time. It just takes time to get used to them and get a feel for what they can and cannot do.
This might be a bit heavy handed, but would encapsulating the different parameters types as classes work?:
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
}
And then have some G methods somewhere:
public void G(BaseStuff stuff1)
{
stuff1.FooBaz();
}
public void G(BaseStuff stuff1, BaseStuff stuff2)
{
stuff1.FooBaz();
stuff2.BarBaz();
}
And you can then call with:
G(new IntStuff(10), new StringStuff("hello"));
G(new StringStuff("hello"), new StringStuff("world"));
You can use code generation to solve this.
Look at Reflection.Emit. You can also generate code with T4 in Visual Studio.
The types are really getting in the way here. You could also try to solve this with a dynamic language or with the C# 4 dynamic keyword.
If you are using c# 4.0 you can do this with the option parameter Or you can use object
Foo(object o)
{
if (o is int){ }
else if (o is string){ }
}
Or you can use the generic method Foo<T>(T o){ }
精彩评论