【问题标题】:How to make a distinction between omitted parameter and parameter default value如何区分省略参数和参数默认值
【发布时间】:2016-12-19 21:21:33
【问题描述】:

我正在编写一个接受许多参数的方法(我们称之为“Bar”)。为方便起见,我设置了一个默认值,以便开发人员在调用我的方法时不必指定一堆“null”。换句话说,我的方法的签名是这样的:

public void Bar(string paramA = null, string paramB = null, ... many more parameters omitted...)

我的方法中的代码检查空值并在这种情况下忽略参数。但是,在某些情况下,我需要区分“默认”空值和开发人员故意设置的空值。

这里有两个示例调用来说明我在说什么。第一个示例省略了所有参数(因此 C# 编译器将在编译时将它们替换为 null),第二个示例开发人员决定为第一个参数指定“null”。

Bar();
Bar(null);

我找到了几篇谈论使用“Option/Some/None”模式的文章。我认为最好的是here

但我正在努力弄清楚如何才能为开发人员提供方便的“默认”值。从概念上讲,我想写这样的东西:

public void Bar(Option<string> paramA = Option.None)

但 C# 编译器抱怨 'Option.None' 不是编译时常量。

【问题讨论】:

  • 如果您有“许多参数”,通常会有一个参数类难以摆脱,您可以随意使用属性进行初始化(而您未分配的参数类将保留其默认值) .这也不需要任何时髦的Option 类。如果您需要在下一个版本中更改默认值,它也不会中断。
  • 或者你可以添加一个额外的参数bool useA = true,如果用户不想使用A,他可以将useA设置为false。但是,在实践中,您最好像其他人建议的那样添加一些重载或使用类作为参数。

标签: c#


【解决方案1】:

C#有“重载”的概念,即同名但参数不同的C#方法。

private void MethodA(string param1) {

}

private void MethodA(string param1, string param2) {

}

如果这不是您想要的,您也可以使用构建器模式。 Builder 可能是一个减少内存分配的结构,如果你担心这种事情的话。

var result = new Builder()
  .SetParam1(value)
  .SetParam2(value)
  .Build();

【讨论】:

  • 不幸的是,我有太多参数需要考虑重载。过载的数量将无法管理。但是,我需要研究您的“建设者”建议。
【解决方案2】:

不,没有办法区分它们。事实上,“默认”值是编译器在调用中烘焙出来的,所以

Bar();

Bar(null);

产生等效的 IL。

通常我会推荐重载而不是默认值,但由于您有多个参数,重载的数量会呈指数增长。

如果您需要区分 null 和“默认”值,则需要使用其他值作为默认值。

另一种选择是创建一个可以存储所有参数值的类型。然后你可以判断一个“参数”是设置为null还是默认为null,因为你可以挂钩到属性设置器。

【讨论】:

  • 你可能是对的:我最好的选择可能是存储所有参数的类
【解决方案3】:

这基本上就是 Nullable 的用途。虽然它用于结构,但您可以创建自己的包装类来做同样的事情(使用强制转换运算符等)。

例如,您可以创建外观和行为类似于 T 的 Argument,但有一个名为 Set 的属性返回一个布尔值。

然后你可以定义你的方法:

public void Bar(Argument<string> paramA = null, ...)

这是一个完整的例子,但我不得不承认我对此并不满意。当作为参数传递时,似乎我无法让隐式转换运算符得到尊重。几年前,当他们进行协变和逆变转换时,我曾认为 C# 添加了这种强制。也许我记错了。所以它仍然需要强制转换(我正在通过扩展方法进行)。

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Calling Foo(default(string).ToArg())...");
        Foo(default(string).ToArg());

        Console.WriteLine("Calling Foo(((string)null).ToArg())...");
        Foo(((string)null).ToArg());

        Console.WriteLine("Calling Foo(\"test\".ToArg())...");
        Foo("test".ToArg());

        Console.WriteLine("Calling Foo()...");
        Foo();
        Console.ReadLine();
    }

    public static void Foo(Argument<string> arg1 = null)
    {
        Console.WriteLine("arg1 is {0}null", arg1 == null ? "" : "not ");
        Console.WriteLine("arg1.IsSet={0}", arg1?.IsSet ?? false);
        Console.WriteLine("arg1.Value={0}", arg1?.Value ?? "(null)");
    }

}

public class Argument<T>
{
    public Argument()
    {
        IsSet = false;
    }

    public Argument(T t)
    {
        _t = t;
        IsSet = true;
    }

    private T _t;

    public T Value { get { return _t; } set { _t = value; IsSet = true; } }
    public bool IsSet { get; private set; }
    public static implicit operator T(Argument<T> t) { return t._t; }

}

public static class Extensions
{
    public static Argument<string> ToArg(this string s) { return new Argument<string>(s); }

}

【讨论】:

    【解决方案4】:

    根据类型,您可以执行以下操作:

    public static void Bar(string a = "", int b = 0) {
    

    因为它们都没有值,但仍然可以选择为空。 对于布尔值,我只会默认为 false,因为布尔值是不可为空的。 所以:

    public static void Bar(string a = "", int b = 0) {
    if (a == "") {
    // if there is no parameter
    }
    else {
    // stuff
    }
    if (b == 0) {
    // no param
    }
    else {
    // stuff
    }
    

    希望我能帮上忙!

    【讨论】:

    • 很抱歉,我看不到您的建议如何帮助我区分省略的参数和故意设置为 null 的参数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-03
    • 1970-01-01
    • 1970-01-01
    • 2016-03-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多