【问题标题】:C# optional parameter besides null for class parameter?除了类参数的null之外的C#可选参数?
【发布时间】:2012-05-23 20:32:45
【问题描述】:

这个问题的最佳解决方案是什么?我正在尝试创建一个函数,该函数具有多个类类型的可选参数,其中 null 是一个有意义的值,不能用作默认值。如,

public void DoSomething(Class1 optional1, Class2 optional2, Class3 optional3)
    {
        if (! WasSpecified(optional1)) { optional1 = defaultForOptional1; }
        if (! WasSpecified(optional2)) { optional2 = defaultForOptional2; }
        if (! WasSpecified(optional3)) { optional3 = defaultForOptional3; }

        // ... 做实际的工作 ...
    }

我不能使用Class1 optional1 = null,因为 null 是有意义的。我不能使用一些占位符类实例Class1 optional1 = defaultForOptional1,因为这些可选参数的编译时常量要求我提出了以下选项:

  1. 为所有可能的组合提供重载,这意味着此方法有 8 个重载。
  2. 为每个可选参数包含一个布尔参数,指示是否使用默认值,我会弄乱签名。

有没有人为此想出一些聪明的解决方案?

谢谢!

编辑:我最终编写了一个包装类,因此我不必重复Boolean HasFoo

    /// <summary>
    /// A wrapper for variables indicating whether or not the variable has
    /// been set.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public struct Setable<T>
    {
        // According to http://msdn.microsoft.com/en-us/library/aa288208%28v=vs.71%29.aspx,
        // "[s]tructs cannot contain explicit parameterless constructors" and "[s]truct
        // members are automatically initialized to their default values."  That's fine,
        // since Boolean defaults to false and usually T will be nullable.

        /// <summary>
        /// Whether or not the variable was set.
        /// </summary>
        public Boolean IsSet { get; private set; }

        /// <summary>
        /// The variable value.
        /// </summary>
        public T Value { get; private set; }

        /// <summary>
        /// Converts from Setable to T.
        /// </summary>
        /// <param name="p_setable"></param>
        /// <returns></returns>
        public static implicit operator T(Setable<T> p_setable)
        {
            return p_setable.Value;
        }

        /// <summary>
        /// Converts from T to Setable.
        /// </summary>
        /// <param name="p_tee"></param>
        /// <returns></returns>
        public static implicit operator Setable<T>(T p_tee)
        {
            return new Setable<T>
            {
                IsSet = true
              , Value = p_tee
            };
        }
    }

【问题讨论】:

  • 重载不会让你传递文字空常量。 (只是风格问题。)
  • 你到底想在这里完成什么?

标签: c# .net


【解决方案1】:

我至少会考虑为参数创建一个新类型:

public void DoSomething(DoSomethingOptions options)

... DoSomethingOptions 可能如下所示:

public class DoSomethingOptions
{
    private Class1 class1;
    public bool HasClass1 { get; private set; }

    public Class1 Class1 
    {
        get { return class1; }
        set
        {
            class1 = value;
            HasClass1 = true;
        }
    }

    ... for other properties ...
}

然后你可以调用它:

DoSomething(new DoSomethingOptions { Class1 = null, Class2 = new Class2() });

您最终不会得到一组指数级的重载,您仍然可以合理紧凑地调用它。

这类似于ProcessProcessStartInfo 采用的方法。

【讨论】:

  • 谢谢你的想法,乔恩。除非我听说有办法解决编译时常量问题,否则我会继续这样做。
【解决方案2】:

为每个可能的组合提供重载,这意味着此方法有 8 个重载。

这是我的偏好。它使情况非常清晰和可维护。在内部,您可以映射到单个初始化例程以减少重复代码。

【讨论】:

  • 在这种情况下(8 次重载),它可以被认为有些可维护。但是,由于需要更多参数,这显然是可维护的。想象一下,如果他有 5 个参数……那就是 32 种变化!是的,它以自己的方式更加清晰,但可维护性受到了巨大的打击。话又说回来,CA1026 msdn.microsoft.com/en-us/library/ms182135.aspx 确实声明不使用可选参数进行公共访问......这真是太疯狂了。
  • @m-y 实际上,很少有每个参数都是或应该是可选的 - 所以它只是有意义的参数。如果每个参数都是可选的,我会重构为更有意义的数据结构。
  • @ReedCopsey,这与 Jon Skeet 在下面的回答一致。我很可能会按照您和 Jon 的建议使用传输对象,因为我可能有三个以上的可选参数。 m-y 是对的,变化会变得过大。
  • @ReedCopsey 您的第二条评论,即与 Jon Skeet 的回答一致。
【解决方案3】:

我更愿意让null 表示“无”,并在Class1Class2 等上拥有一个Class1Class2 等类型的static readonly 成员,命名为None。然后,您可以使用 null 作为“无”,而不是使 null 有意义。

如果这令人困惑:

public class Class1
{
    public static readonly Class1 None = new Class1();
}
public static Class2
{
    public static readonly Class2 None = new Class2();
}

请注意,如果null 在您的情况下表示“无”以外的其他内容(如“MissingData”或其他内容),您应该这样命名该成员。另请注意:这对于将来阅读和使用您的代码的其他人来说会更有意义。

【讨论】:

  • 另见空对象模式以获取相关信息:en.wikipedia.org/wiki/Null_Object_pattern(和 +1)
  • +1 - 是的,我完全同意。与其去掉 null 的 true 含义,不如使用一个表示“Nothing”值的静态字段。这有点像 String.Empty 与 null。
  • 就像我说的,我试图避免使用null 来表示“无”或“未指定”。在这种情况下,null 是有意义的。在您的示例中,我将如何使用 Class1.None 作为默认参数而不获得“'optional1' 的默认参数值必须是编译时常量”? public void DoSomething(Class1 optional1 = Class1.None) 让我知道那个错误。谢谢大家的回复。
  • @Don,您介意解释一下 如何 null 是有意义的吗?您正在与 C# 的语义作斗争...此外,您不会使用 Class.None 作为默认参数,您将使用 null,然后在方法中您首先要说, class1Parameter = class1Parameter ?? Class1.None
  • @Don01001100:你会改变用法...Class1.None 将代表你现在使用 null 来表示的任何内容,而 null 将代表它自己。因此,您将所有可选参数默认为 null 并使用 Class1.None 替换 null 所代表的任何内容。不要使用 null 来赋予除 null 以外的其他含义。再次查看String.Empty。从字面上看,它是一个表示“Nothing”的字符串,而不是使用 null 表示“Nothing”。
【解决方案4】:

您可以创建一个Flags 枚举,您可以将其传递以标记要使用的类。

[Flags]
public enum DoSomethingOptions
{
    None = 0,
    UseClass1 = 1,
    UseClass2 = 2,
    UseClass3 = 4,
    etc..
}

DoSomething(Class1 class1, ..., DoSomethingOptions options = DoSomethingOptions.None) { ... }

然后只需将该枚举传入以标记要使用的类。我很奇怪为什么你用null 来表示null 以外的东西?虽然这可能是一个解决方案,但我真的想说“重新考虑你的设计”。

【讨论】:

    【解决方案5】:

    是的,尝试使用一个对象。定义一个封装可能选择的类。在对象中设置选项时,如果它是通过使用原始属性的设置器设置的,则可以将其存储在同一对象中。

    一个例子:

    internal class SettingsHolder
    {
        public SettingsHolder()
        {
            IsOriginalPropADefault = true;
        }
    
        private Class1 originalProp;
        public Class1 OriginalProp
        {
            get
            {
                return originalProp;
            }
            set
            {
                originalProp = value;
                IsOriginalPropADefault = false;
            }
        }
    
        public bool IsOriginalPropADefault { get; private set; }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2021-05-06
      • 2017-02-19
      • 2016-06-12
      • 2014-05-27
      • 2011-03-25
      • 1970-01-01
      • 2018-11-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多