Improving Variable Parameter Methods Using .NET Generics

I have many functions that are currently overloaded to work on int and string :

 bool foo(int); bool foo(string); bool bar(int); bool bar(string); void baz(int p); void baz(string p); 

Then I have many functions that take 1, 2, 3, or 4 arguments, either int , or string , that call the above functions:

 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. 

Note The implementation of the g() family is just an example

More types can be entered at any time than the current int or string . The same goes for functions with more arguments than 4. The current number of identical functions is little controllable. Add one more option in any dimension, and the combinatorial explosion will be so huge that it can blow away the application.

In C ++, I would plan for g() and execute.

I understand that the varieties of .NET are different. I struggled with them for two hours, trying to come up with a solution that does not require copying and pasting the code, but to no avail.

C # generators will not require me to enter identical code for a family of functions with five arguments of any of the 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 that bar() and baz() can read both int and string , and the g() family defines the type and number of arguments to parse (implicitly, by type of argument).

+4
source share
10 answers

Not as optimal as we would like ... but what if foo , bar and baz also had common versions?

 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); } 
+2
source

Consider using inheritance for this case. I assume that foo , bar and baz are inherent to the type (int or string in your case). If this is not correct, correct or comment on 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 will lead to the conclusion:

 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 the new supported type, you:

  • create a new class that supports the type and extends Param<T> ;
  • implement foo , bar and baz for this new type;
  • Create a new g method (only one) that has a different parameter.

Especially for 3) it will significantly reduce the explosion of methods. Now you write one method g for any given number of parameters. With the previous design, you had to write parameters n , methods 2^n (n = 1 β†’ 2 methods, n = 2 β†’ 4 methods, n = 3 β†’ 8 methods, ...).

+5
source

Your real problem here is most likely one of the projects, not something that generics can be used for. Generics should be used for things that are not really agnostics, not tricks to make life a little easier. Perhaps try posting the code of the actual example that you are using, and someone might have an idea on how to reverse engineer the solution so that you can extend it without such a big 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 can call it like this:

 DoSomethingConditionally<String>("input", v => v == "hello", s => Console.WriteLine(s)); 

I used lambda expressions here, but you could easily predefine some Func<> that execute some common expressions. It will be much better than method overloading, and will force you to process new input types during development.

+4
source

Use a list of objects.

In the case when the number of parameters is unknown during planning, just use the list of objects. Sort of:

 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 types, since each link is obtained from an object (which can be much more types than required, int and string, but may be convenient in the future to support other types).

This is possible because you can use Reflection to recognize the type at runtime.

Another way to perform a sequence of operations is to use interfaces, to define an action to perform, under certain conditions, 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 in this way you need to model p1 and p2 (but also p3, as objects that implement the interface, which is impossible.

+1
source

If you are using C # /. NET 4.0, you can do multiple submitting using a dynamic function, so you only need to implement one g overload based on the number of arguments and the correct foo / bar / baz overloads by type inside each g implementation will be allowed during fulfillment.

  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 if you cannot use C # /. NET 4.0, you can still use this approach using reflection. I added another overload of foo / bar / baz to double display how well this generalizes and eliminates 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); } 
+1
source

I would do it as a comment on @smink, but I miss the rep ...

If you extend the Param base class to have implicit statements, you are back to the fact that you did not have to wrap the contents in code (although the execution time still carries overhead) ...

 abstract class Param { ... public static implicit operator Param(int value) { return new IntParam(value); } } 
+1
source

Unfortunately, generics cannot handle this situation. At least not very well. If you make your methods generalized, then any type can be passed to them. There will be no adequate where clause for generics to limit it to just a string and int. If your methods are going to perform certain operations related to int / string inside them, then generators will not work at all.

Generics in C # are not as strong as C ++ templates, and yes, they can cause some serious headaches on time. It just takes time to get used to them and understand what they can and cannot do.

0
source

This might be a little tricky, but will encapsulate different types of parameters 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 somewhere there are some G methods:

 public void G(BaseStuff stuff1) { stuff1.FooBaz(); } public void G(BaseStuff stuff1, BaseStuff stuff2) { stuff1.FooBaz(); stuff2.BarBaz(); } 

And you can call using:

 G(new IntStuff(10), new StringStuff("hello")); G(new StringStuff("hello"), new StringStuff("world")); 
0
source

You can use code generation to solve this problem.

See Reflection.Emit . You can also generate code with T4 in Visual Studio.

Types really get in the way here. You can also try to solve this problem using a dynamic language or using the C # 4 dynamic keyword.

0
source

If you are using C # 4.0, you can do this using the parameter parameter Or you can use an object

 Foo(object o) { if (o is int){ } else if (o is string){ } } 

Or you can use the generic method Foo<T>(T o){ }

-1
source

Source: https://habr.com/ru/post/1313206/


All Articles