Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compile-time method call validation for multiple parameters of the same type

Here is demonstration of the problem:

class Program
{
    static double Func(double a, double b) { return a * 1000 + b * b; }

    static void Main(string[] args)
    {
        var a = 1.1d;
        var b = 2.2d;
        Console.WriteLine(Func(a, b));
        // this is the problem, function doesn't recognize when a and b
        // "accidentally" exchanged, target is to make this row a compile-time error
        Console.WriteLine(Func(b, a));
    }
}

This become an issue if there are methods with many parameters (e.g. ten of double type):

double Func(double parameter1, double parameter2, ..., double parameter10);

Question: is there a way to validate parameters when calling method, so that programmer is less prone to do a mistake?


This is not an issue if parameter types are different. I thought what maybe wrapping into new types will help:

class A
{
    private double _value;
    public static implicit operator A(double value) { return new A() { _value = value }; }
    public static implicit operator double(A value) { return value._value; }
}
class B
{
    private double _value;
    public static implicit operator B(double value) { return new B() { _value = value }; }
    public static implicit operator double(B value) { return value._value; }
}

class Program
{
    static double Func(A a, B b) { return a * 1000 + b * b; }

    static void Main(string[] args)
    {
        A a = 1.1d;
        B b = 2.2d;
        Console.WriteLine(Func(a, b));
        Console.WriteLine(Func(b, a)); // compile-time error! yay!
        Console.WriteLine(Func(a, b) + 123.123d - a * 2); // implicit conversion power
        Console.ReadKey();
    }
}

And it does, but I am quite unsure if this method is efficient. I have doubts if this is a good idea at all. Is it? Or is there better one?

I know what I can be absolutely safe if I always call method like this (using named arguments method call)

Func(a:a, b:b);

This shouldn't bring any overhead in code, but a lot of typing. Wrapping is better because it is done once (creating new type is easy), but it probably has overhead.

like image 388
Sinatr Avatar asked May 07 '15 10:05

Sinatr


2 Answers

If two arguments are of the same type, it's not possible to detect at compile-time, run-time or otherwise that the name of the argument variable corresponds to the name of the parameter. This is kind of an open question, but I will offer you a couple ideas.

  • As Mehrzad suggested, consider grouping parameters by some type. For example, instead of double Distance(double x1, double y1, double x2, double y2), consider double Distance(Point p1, Point p2)

  • In general, if your method has more than 4-5 parameters, consider refactoring. Maybe your method is trying to do too many things and the logic can be divided?

  • If what you actually want to do is to perform some check such as ensuring that a < b, consider looking into Code contracts. You could also use Debug.Assert(), but this only works at run-time.

  • I wouldn't recommend the kind of implicit conversion you propose. For me, it feels hacky and unintuitive that A a = 1.1 should have no semantic purpose other than compile-time checking parameters. Your ultimate goal is to make code more maintainable overall.

like image 68
MariusUt Avatar answered Oct 18 '22 09:10

MariusUt


You should never have 10 parameters for a method.

Once you have around 4 parameters, begin to consider the use of a new class to contain those parameters... As an example, consider the preferences of a user navigating on a website...

void Main()
{
    UserPreferences preference = new UserPreferences
    {
        BackgroundColor = "#fff",
        ForegroundColor = "#000",
        Language = "en-GB",
        UtcOffSetTimeZone = 0
    };

    User aydin = new User(preference);
}

public class User
{
    public User(UserPreferences preferences)
    {
        this.Preferences = preferences;
    }

    public UserPreferences Preferences { get; set; }
}

public class UserPreferences
{
    public string BackgroundColor { get; set; }
    public string ForegroundColor { get; set; }
    public int UtcOffSetTimeZone { get; set; }
    public string Language { get; set; }
}
like image 45
Aydin Avatar answered Oct 18 '22 10:10

Aydin