Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Improving methods with variable parameters using .NET Generics

Tags:

c#

.net

generics

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(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 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).

like image 350
sbi Avatar asked Dec 13 '22 21:12

sbi


1 Answers

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:

  1. create a new class that supports the type and extends Param<T>;
  2. implement Foo, Bar and Baz for that new type;
  3. 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, ..).

like image 63
Jorge Ferreira Avatar answered Jan 04 '23 23:01

Jorge Ferreira