简体   繁体   中英

C# optional parameter besides null for class parameter?

What is the best solution to this problem? I'm trying to create a function that has several optional parameters of class types for which null is a meaningful value and cannot be used as a default. As in,

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

I can't use Class1 optional1 = null because null is meaningful. I can't use some placeholder class instance Class1 optional1 = defaultForOptional1 because of the compile-time constant requirement for these optional parameters I've come up with the following options:

  1. Provide overloads with every possible combination, which means 8 overloads for this method.
  2. Include a Boolean parameter for each optional parameter indicating whether or not to use the default, which I clutters up the signature.

Has anyone out there come up with some clever solution for this?

Thanks!

edit: I ended up writing a wrapper class for so I didn't have to keep repeating 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
            };
        }
    }

I would at least consider creating a new type for the parameter:

public void DoSomething(DoSomethingOptions options)

... where DoSomethingOptions could look like this:

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

Then you can call it with:

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

You don't end up with an exponential set of overloads, and you can still call it reasonably compactly.

This is similar to the approach that Process takes with ProcessStartInfo .

Provide overloads with every possible combination, which means 8 overloads for this method.

This is my preference. It makes the situation very clear and maintainable. Internally, you can map to a single initialization routine to reduce duplicated code.

I'd prefer making null mean "nothing," and have a static readonly member of type Class1 , Class2 , etc. on Class1 , Class2 etc. named None . Then instead of making null meaningful you can use null as "nothing" as it was originally intended.

In case that's confusing:

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

Note, that if null in your case means something other than "None" (like "MissingData" or something else) you should name the member thusly. Also note: this will make a lot more sense to other people reading and using your code in the future.

You can create a Flags enumeration that you can pass along to mark which classes to use.

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

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

Then simply pass that enumeration in to mark which classes to use. I do wonder why you used null to mean something other than ? 以外的东西? Although this might be a solution, I'd really like to say "rethink your design".

Yeah, try using an object. Define a class which encapsulates the possible choices. When a choice is set in the object you can store in that same object if it was set through the use of the setter of the original property.

An example:

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

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM