【问题标题】:Compile-time method call validation for multiple parameters of the same type同一类型的多个参数的编译时方法调用验证
【发布时间】:2015-05-07 10:25:19
【问题描述】:

这是问题的演示:

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));
    }
}

如果有许多参数的方法(例如double 类型的十个),这将成为一个问题:

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

问题:有没有办法在调用方法时验证参数,让程序员更不容易出错?


如果参数类型不同,这不是问题。我认为可能包装到新类型会有所帮助:

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();
    }
}

确实如此,但我不确定这种方法是否有效。我怀疑这是否是一个好主意。是吗?还是有更好的?

我知道如果我总是调用这样的方法(使用named arguments 方法调用)我可以绝对安全

Func(a:a, b:b);

这不应该带来任何代码开销,但需要大量输入。包装更好,因为它只完成一次(创建新类型很容易),但它可能有开销。

【问题讨论】:

  • 如果你有十个参数,你可以将每组相关参数包装在一个类型中,你考虑过吗?
  • 可能确保您不会给出错误参数的唯一方法是为 each 参数设置不同的类型,并且从不在它们的参数中使用它们展开的形式。它将主要关闭一种可能的犯错途径。无论是不是现实的方法,是否真的能提高单行缺陷率和整个程序的可维护性,都是another question
  • @MehrzadChehraz,好主意。听起来像是给我的答案,我只需要考虑一下,给我一些时间。

标签: c# methods compiler-errors


【解决方案1】:

如果两个参数属于同一类型,则无法在编译时、运行时或其他情况下检测参数变量的名称是否与参数的名称相对应。这是一个悬而未决的问题,但我会提供一些想法。

  • 按照 Mehrzad 的建议,考虑按某种类型对参数进行分组。例如,不要考虑double Distance(double x1, double y1, double x2, double y2),而是考虑double Distance(Point p1, Point p2)

  • 一般来说,如果您的方法有超过 4-5 个参数,请考虑重构。可能你的方法做的东西太多,逻辑可以分?

  • 如果您真正想做的是执行一些检查,例如确保a < b,请考虑查看Code contracts。您也可以使用Debug.Assert(),但这仅在运行时有效。

  • 我不推荐您建议的那种隐式转换。对我来说,A a = 1.1 除了编译时检查参数之外应该没有任何语义目的,这感觉很笨拙和不直观。您的最终目标是让代码整体更易于维护。

【讨论】:

  • 分组是个好主意,但在我的实际情况中并不适用。重构实际上是不可能的,因为这个方法必须在很多地方调用,一些参数可以是某个对象的属性或者必须指定。事实上,我总是可以要求为方法创建对象(根据@AydinAdn 的回答),或者更确切地说使方法非静态(所以在调用方法之前我必须创建一个实例并在创建期间填充属性,然后在方法中使用) .关于最后一点,请考虑使用Func(a, 123);,其中B 是透明的double。但是,是的,我也觉得它很老套。
  • 你的方法具体是做什么的?有许多命名参数的方法是不好的,但有时这是不可避免的。您能否至少发布您的方法的签名?
  • 方法用于使用给定的数据数组和更多参数计算统计值,我们称之为配置,其中一些属于静态配置,一些属于数据特定的动态,一些调整方法结果本身(不属于任何一种配置)。我想在一个地方有公式(显然),我不想将完整的配置传递给方法(与动态配置耦合并不聪明),这就是为什么我更喜欢无名无实体double 参数。
  • 我想保留@MehrzadChehraz 给我的评论的想法:删除静态方法并使它们成为配置的一部分。我将使用当前配置实例(已设置所有属性)来调用方法,或者创建一个新配置,设置所需的属性,然后调用方法。听起来像是整体移除参数。
  • 我不建议使用全局配置运行 - 将来会出现可维护性问题。传递配置对象对我来说似乎是一个不错的选择
【解决方案2】:

一个方法永远不应该有 10 个参数。

一旦您有大约 4 个参数,就开始考虑使用一个新类来包含这些参数...例如,考虑用户在网站上导航的偏好...

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; }
}

【讨论】:

  • 创建一个专用类型来保存方法的参数是个好主意。但是,如果有很多这样的情况,那就不再是了。在给定的示例中,UserPreferencesUser 的属性。没有真正需要专门为构造对象创建 UserPreference 类型,它们可以是 User 的属性并在构造后填充,除非您在其他地方使用 UserPreferences 类型(例如,将首选项从一个用户复制到另一个用户,会比复制属性值更优雅)。
  • 是的,在现实世界中,您不需要UserPreference 对象来创建User,但它比具有4 个参数的构造函数要优雅得多,我只是想分享一下这个概念它的自我
【解决方案3】:

使用类似这样的继承类

    class Program
    {
        static double Func(List<Parent> l) { return l[0]._value * 1000 + l[1]._value * l[1]._value; }

        static void Main(string[] args)
        {
            A a = 1.1d;
            B b = 2.2d;
            Console.WriteLine(Func(new List<Parent>() {a,b}));
            Console.WriteLine(Func(new List<Parent>() { a, b })); // compile-time error! yay!
            Console.WriteLine(Func(new List<Parent>() { a, b }) + 123.123d - a * 2); // implicit conversion power
            Console.ReadKey();
        }
    }
    class Parent
    {
        public double _value { get; set; }
    }
    class A : Parent
    {
        public static implicit operator A(double value) { return new A() { _value = value }; }
        public static implicit operator double(A value) { return value._value; }
    }
    class B : Parent
    {
        public static implicit operator B(double value) { return new B() { _value = value }; }
        public static implicit operator double(B value) { return value._value; }
    }

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多