除了 null 之外,C# 中类参数的可选参数是什么?

10
什么是解决此问题的最佳方案?我正在尝试创建一个函数,该函数具有多个可选参数,这些可选参数是类类型,其中 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; }
// ... do the actual work ... }
我不能使用 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
            };
        }
    }

1
重载不允许传递字面上的 null 常量。(这只是一个样式问题。) - Neil
你在这里究竟想要实现什么? - myermian
5个回答

12

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

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

你不会得到一组指数级别的重载,并且仍然可以相对紧凑地调用它。

这类似于 Process 使用 ProcessStartInfo 的方法。


1
谢谢你的建议,Jon。除非我找到解决编译时常量问题的方法,否则我会选择这个方案。 - Becca Dee

7

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

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


3
在这种情况下(8个超载),它可以被认为是有点可维护的。但是,如果需要更多的参数,显然就无法再保持可维护性了。想象一下如果有5个参数...那就有32种变化!是的,从某种意义上来说它更加清晰,但可维护性会受到很大影响。再说,CA1026 http://msdn.microsoft.com/en-us/library/ms182135.aspx确实指出不要在公共访问中使用可选参数...这只是一种疯狂的做法。 - myermian
1
@m-y 实际上,每个参数都是可选的或应该是可选的情况很少见,只有有意义的参数才是可选的。如果每个参数都是可选的,我会重构成更有意义的数据结构。 - Reed Copsey
@ReedCopsey,这与Jon Skeet在下面的答案一致。 我很可能会像你和Jon建议的那样使用传输对象,因为我可能有超过三个可选参数。 m-y认为变化会变得过多,这一点是正确的。 - Becca Dee
@ReedCopsey,你的第二条评论与Jon Skeet的答案一致。 - Becca Dee

6
我更喜欢将 null 解释为“无”,并在 Class1Class2 等类中添加类型为 static readonly 的成员,命名为 None。这样,你就可以像最初设计的那样将 null 用作“无”而不是赋予它其他含义。

如果这有点令人困惑:

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

请注意,如果在您的情况下null表示的不是“None”(比如“MissingData”或其他内容),您应该相应地命名成员。另请注意:这将使未来阅读和使用您的代码的其他人更容易理解。

+1 - 是的,我完全同意。不应该剥夺 null 的真实含义,而是应该使用一个静态字段来表示“空值”。这有点像 String.Empty 和 null 的区别。 - myermian
1
就像我说的,我试图避免使用null来表示“无”或“未指定”。在这种情况下,null是有意义的。在你的例子中,如果我要使用Class1.None作为默认参数,该怎么办,而不会出现“必须是编译时常量的默认参数值'optional1'”的错误?public void DoSomething(Class1 optional1 = Class1.None)会导致这个错误。谢谢大家的回复。 - Becca Dee
@Don,你介意解释一下null如何有意义吗?你正在与C#的语义作斗争...另外,你不会使用Class.None作为默认参数,而是使用null,然后在方法内部你会开始说,class1Parameter = class1Parameter ?? Class1.None - Chris Pfohl
1
@Don01001100:你可以反转使用方式... Class1.None 将代表你现在使用 null 代表的任何内容,而 null 将代表它本身。所以你需要将所有可选参数默认为 null,并使用 Class1.None 替换 null 所代表的内容。不要使用 null 来赋予除了 null 本身之外的其他含义。再看看 String.Empty。它实际上是一个表示 "Nothing" 的字符串,而不是使用 null 来表示 "Nothing"。 - myermian
谢谢回复。现在我明白了。基本上,我想要的是将类的任何实例、null或"未指定"作为参数提供给这个方法。如果您不指定,该方法将为您创建一些内容。此代码用于创建测试对象,例如,您可以进行一个测试,将Person对象的Car属性设置为null以测试没有汽车的Person,将其设置为特定的Car实例以查看该Person如何与该Car交互,还有一个测试,您不关心,所以您会省略参数并让函数分配一个默认值。 - Becca Dee
显示剩余2条评论

2
你可以创建一个Flags枚举,将其传递以标记要使用哪些类。
[Flags]
public enum DoSomethingOptions
{
    None = 0,
    UseClass1 = 1,
    UseClass2 = 2,
    UseClass3 = 4,
    etc..
}

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

然后只需将枚举传递进去标记使用哪些类。我确实想知道为什么您使用 null 表示其他含义而不是null?尽管这可能是一种解决方案,但我真的希望说“重新考虑设计”。


1

嗯,尝试使用对象。定义一个封装可能选择的类。当在对象中设置选择时,您可以将其存储在同一对象中,以便通过原始属性的setter设置。

一个例子:

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

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接