Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Good morning everyone,

I have decided to create a simple Option type for C#. I have essentially based it on the Scala's Option.

It was mostly a fun exercise, but it might come in handy at work or any other project, even though it's less powerful than the Scala version.

Now I'd appreciate all remarks regarding this little piece of code, but essentially now I'm thinking whether or not to introduce an implicit conversion between the type and the Option, which would allow for:

MyType obj;
// ... something interesting happens (or not)
Option<MyType> optionalObj = obj;

I have already included ToOption extension method for convenient creation of the option from existing object:

var optionalObj = obj.ToOption();

which returns either Some<T> or None<T>and the implicit conversion would do just that, but I'm just wondering if it's not too 'hidden', non-obvious and/or counter-intuitive(?).

Anyway, I'd like to see what you think.

[EDIT] Per comment:

public abstract class Option<T>
{
    /// <summary>
    /// Option's value
    /// </summary>
    public abstract T Value { get; }

    /// <summary>
    /// Indicates whether the option holds a value or not
    /// </summary>
    public abstract bool IsEmpty { get; }

    /// <summary>
    /// Unwraps the option.
    /// </summary>
    /// <param name="defaultValue"></param>
    /// <returns>Specified, default value if the option is empty, or the option's value if present</returns>
    /// <remarks>It's recommended to use the <see cref="GetOrElse(Func{T})"/> when passing a parameter expression to be evaluated.
    /// In this case, the condition will get evaluated AFTER evaluation of the parameter, which may be costly.</remarks>
    public T GetOrElse(T defaultValue)
    {
        return IsEmpty ? defaultValue : Value;
    }

    /// <summary>
    /// Unwraps the option
    /// </summary>
    /// <param name="defaultValue">Function returning the default value</param>
    /// <returns>The evaluation of the specified function if option is empty, or the option's value if present</returns>
    /// <remarks>Recommended overload. The function returning defaultValue will only get evaluated if
    /// the option is empty.</remarks>
    public T GetOrElse(Func<T> defaultValue)
    {
        return IsEmpty ? defaultValue() : Value;
    }

    /// <summary>
    /// Unwraps the option
    /// </summary>
    /// <returns>The default value for the type if option is empty, or the option's value if present</returns>
    public T GetOrDefault()
    {
        return GetOrElse(default(T));
    }

    private static readonly Lazy<None<T>> NoneInstance = new Lazy<None<T>>(() => new None<T>());

    /// <summary>
    /// Shared <code>None{T}</code> instance.
    /// </summary>
    public static None<T> None
    {
        get
        {
            return NoneInstance.Value;
        }
    }

    public abstract override string ToString();
}

public sealed class Some<T> : Option<T>
{
    /// <summary>
    /// Creates a new option holding the value.
    /// </summary>
    /// <exception cref="ArgumentNullException">When the value passed is null. <code>None{T}</code> (or the extension method which creates appropriate type) should be used instead.</exception>
    /// <param name="value"></param>
    public Some(T value)
    {
        if(value == null)
            throw new ArgumentNullException("value", "Argument passed to Some was null - use None<T> instead.");

        this.value = value;
    }

    private readonly T value;
    public override T Value
    {
        get
        {
            return value;
        }
    }

    public override bool IsEmpty
    {
        get
        {
            return false;
        }
    }

    public override string ToString()
    {
        return Value.ToString();
    }
}

public sealed class None<T> : Option<T>
{
    /// <summary>
    /// <exception cref="InvalidOperationException">Thrown when accessing the value. <code>None{T}</code> has no value.</exception>
    /// </summary>
    public override T Value
    {
        get
        {
            throw new InvalidOperationException();
        }
    }

    public override bool IsEmpty
    {
        get
        {
            return true;
        }
    }

    public override string ToString()
    {
        return "None";
    }
}

namespace OptionType.Extensions
{
    /// <summary>
    /// Extension methods for <see cref="Option{T}"/>
    /// </summary>
    public static class OptionExtensions
    {
        /// <summary>
        /// Wraps the specified object in an option. If the object is null, returns <see cref="None{T}"/>, otherwise creates <see cref="Some{T}"/>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <returns>A new <see cref="Option{T}"/></returns>
        public static Option<T> ToOption<T>(this T value)
        {
            if (value == null) return Option<T>.None;

            return new Some<T>(value);
        }

        /// <summary>
        /// Applies a specified function to the option's value and yields a new option if the option is non-empty.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="option"></param>
        /// <param name="func"></param>
        /// <returns><see cref="Some{T}"/> if the option is non-empty, <see cref="None{T}"/> otherwise.</returns>
        public static Option<U> Select<T, U>(this Option<T> option, Func<T, U> func)
        {
            if (option.IsEmpty) return Option<U>.None;

            return new Some<U>(func(option.Value));
        }

        /// <summary>
        /// Applies a specified function to the option's value and yields a new option if the option is non-empty.
        /// <remarks>Different from <see cref="Select{T,U}"/>, expects a function that returns an <see cref="Option{T}"/></remarks>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="option"></param>
        /// <param name="func"></param>
        /// <returns><see cref="Some{T}"/> if the option is non-empty, <see cref="None{T}"/> otherwise.</returns>
        public static Option<U> SelectMany<T, U>(this Option<T> option, Func<T, Option<U>> func)
        {
            if (option.IsEmpty) return Option<U>.None;

            return func(option.Value);
        }

        /// <summary>
        /// Filters the option by the passed predicate function
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="option"></param>
        /// <param name="func"></param>
        /// <returns>Returns the option if the option is non-empty and the value underneath satisfied the predicate, <see cref="None{T}"/> otherwise.</returns>
        public static Option<T> Where<T>(this Option<T> option, Func<T, bool> func)
        {
            if (option.IsEmpty || func(option.Value)) return option;

            return Option<T>.None;
        }

        /// <summary>
        /// Checks if the value 'exists' inside the option.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="option"></param>
        /// <param name="func"></param>
        /// <returns><code>true</code> if the option is not empty and if it satisfied the predicate, <see cref="None{T}"/> otherwise.</returns>
        public static bool Any<T>(this Option<T> option, Func<T, bool> func)
        {
            return !option.IsEmpty && func(option.Value);
        }

        /// <summary>
        /// Executes a specified action on the option, if the option is non-empty.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="option"></param>
        /// <param name="action"></param>
        public static void ForEach<T>(this Option<T> option, Action<T> action)
        {
            if (!option.IsEmpty) action(option.Value);
        }

        /// <summary>
        /// Returns the <paramref name="alternative"/> if the specified <paramref name="option"/> is empty. Returns the <paramref name="option"/> itself otherwise.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="option"></param>
        /// <param name="alternative"></param>
        /// <remarks>It's recommended to use <see cref="OrElse{T}(Option{T}, Func{Option{T}}"/> overload.</remarks>
        /// <returns></returns>
        public static Option<T> OrElse<T>(this Option<T> option, Option<T> alternative)
        {
            return option.IsEmpty ? alternative : option;
        }

        /// <summary>
        /// Checks whether the current option is empty; if it is, the <paramref name="alternative"/> function is evaluated and the result is returned. Otherwise,
        /// the <paramref name="option"/> is returned.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="option"></param>
        /// <param name="alternative"></param>
        /// <returns>The current option if it's non-empty and the evaluation result of the alternative function otherwise.</returns>
        public static Option<T> OrElse<T>(this Option<T> option, Func<Option<T>> alternative)
        {
            return option.IsEmpty ? alternative() : option;
        }

        /// <summary>
        /// Converts the option to a sequence (<see cref="IEnumerable{T}"/>)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="option"></param>
        /// <returns>One element sequence containing the option's value if the option was non-empty, empty sequence otherwise</returns>
        public static IEnumerable<T> ToEnumerable<T>(this Option<T> option)
        {
            if (option.IsEmpty) yield break;

            yield return option.Value;
        }
    }
}

[EDIT2] After all the comments and answers, I have introduced a few changes. Here's modified reference type, which has the same semantics as the previous one, save for few changes.

  1. Renamed 'IsEmpty' to 'HasValue'.
  2. Made the Option implicitly sealed by adding internal abstract method.
  3. Removed the abstract ToString().
  4. Added implicit conversion.
  5. Got rid of Lazy in favor of simple static readonly field.

Decision whether to leave the methods as extensions or not is mostly philosophical, they can be included into the class or not, I don't think it makes any difference in this case.

I feel against implementing IEnumerable, Option may be treated as a sequence with 0 or 1 element, but (IMO) it isn't technically a sequence. So an explicit 'conversion' to IEnumerable may be clearer, but I might be very well wrong here.

public abstract class Option<T>
{
    /// <summary>
    /// Option's value
    /// </summary>
    public abstract T Value { get; }

    /// <summary>
    /// Indicates whether the option holds a value or not
    /// </summary>
    public abstract bool HasValue { get; }

    /// <summary>
    /// Unwraps the option.
    /// </summary>
    /// <param name="defaultValue"></param>
    /// <returns>Specified, default value if the option is empty, or the option's value if present</returns>
    /// <remarks>It's recommended to use the <see cref="GetOrElse(Func{T})"/> when passing a parameter expression to be evaluated.
    /// In this case, the condition will get evaluated AFTER evaluation of the parameter, which may be costly.</remarks>
    public T GetOrElse(T defaultValue)
    {
        return !HasValue ? defaultValue : Value;
    }

    /// <summary>
    /// Unwraps the option
    /// </summary>
    /// <param name="defaultValue">Function returning the default value</param>
    /// <returns>The evaluation of the specified function if option is empty, or the option's value if present</returns>
    /// <remarks>Recommended overload. The function returning defaultValue will only get evaluated if
    /// the option is empty.</remarks>
    public T GetOrElse(Func<T> defaultValue)
    {
        return !HasValue ? defaultValue() : Value;
    }

    /// <summary>
    /// Unwraps the option
    /// </summary>
    /// <returns>The default value for the type if option is empty, or the option's value if present</returns>
    public T GetOrDefault()
    {
        return GetOrElse(default(T));
    }

    public static implicit operator Option<T>(T value)
    {
        return From(value);
    }

    public static Option<T> From(T value)
    {
        if (value == null)
            return None;

        return new Some<T>(value);
    }

    public static readonly None<T> None = new None<T>(); 

    internal abstract void ImplicitlySealed();
}

public sealed class Some<T> : Option<T>
{
    /// <summary>
    /// Creates a new option holding the value.
    /// </summary>
    /// <exception cref="ArgumentNullException">When the value passed is null. <code>None{T}</code> (or the extension method which creates appropriate type) should be used instead.</exception>
    /// <param name="value"></param>
    public Some(T value)
    {
        if(value == null)
            throw new ArgumentNullException("value", "Argument passed to Some was null - use None<T> instead.");

        this.value = value;
    }

    private readonly T value;
    public override T Value
    {
        get
        {
            return value;
        }
    }

    public override bool HasValue
    {
        get
        {
            return true;
        }
    }

    internal override void ImplicitlySealed()
    {
    }

    public override string ToString()
    {
        return Value.ToString();
    }
}

public sealed class None<T> : Option<T>
{
    /// <summary>
    /// <exception cref="InvalidOperationException">Thrown when accessing the value. <code>None{T}</code> has no value.</exception>
    /// </summary>
    public override T Value
    {
        get
        {
            throw new InvalidOperationException();
        }
    }

    public override bool HasValue
    {
        get
        {
            return false;
        }
    }

    internal override void ImplicitlySealed()
    {
    }

    public override string ToString()
    {
        return "None";
    }
}

namespace OptionType.Extensions
{
    /// <summary>
    /// Extension methods for <see cref="Option{T}"/>
    /// </summary>
    public static class OptionExtensions
    {
        /// <summary>
        /// Wraps the specified object in an option. If the object is null, returns <see cref="None{T}"/>, otherwise creates <see cref="Some{T}"/>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <returns>A new <see cref="Option{T}"/></returns>
        public static Option<T> ToOption<T>(this T value)
        {
            return Option<T>.From(value);
        }

        /// <summary>
        /// Applies a specified function to the option's value and yields a new option if the option is non-empty.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="option"></param>
        /// <param name="func"></param>
        /// <returns><see cref="Some{T}"/> if the option is non-empty, <see cref="None{T}"/> otherwise.</returns>
        public static Option<U> Select<T, U>(this Option<T> option, Func<T, U> func)
        {
            if (!option.HasValue) 
                return Option<U>.None;

            return Option<U>.From(func(option.Value));
        }

        /// <summary>
        /// Applies a specified function to the option's value and yields a new option if the option is non-empty.
        /// <remarks>Different from <see cref="Select{T,U}"/>, expects a function that returns an <see cref="Option{T}"/></remarks>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="option"></param>
        /// <param name="func"></param>
        /// <returns><see cref="Some{T}"/> if the option is non-empty, <see cref="None{T}"/> otherwise.</returns>
        public static Option<U> SelectMany<T, U>(this Option<T> option, Func<T, Option<U>> func)
        {
            if (!option.HasValue) 
                return Option<U>.None;

            return func(option.Value);
        }

        /// <summary>
        /// Filters the option by the passed predicate function
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="option"></param>
        /// <param name="func"></param>
        /// <returns>Returns the option if the option is non-empty and the value underneath satisfied the predicate, <see cref="None{T}"/> otherwise.</returns>
        public static Option<T> Where<T>(this Option<T> option, Func<T, bool> func)
        {
            if (option.HasValue && func(option.Value)) 
                return option;

            return Option<T>.None;
        }

        /// <summary>
        /// Checks if the value 'exists' inside the option.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="option"></param>
        /// <param name="func"></param>
        /// <returns><code>true</code> if the option is not empty and if it satisfied the predicate, <see cref="None{T}"/> otherwise.</returns>
        public static bool Any<T>(this Option<T> option, Func<T, bool> func)
        {
            return option.HasValue && func(option.Value);
        }

        /// <summary>
        /// Executes a specified action on the option, if the option is non-empty.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="option"></param>
        /// <param name="action"></param>
        public static void ForEach<T>(this Option<T> option, Action<T> action)
        {
            if (option.HasValue) 
                action(option.Value);
        }

        /// <summary>
        /// Returns the <paramref name="alternative"/> if the specified <paramref name="option"/> is empty. Returns the <paramref name="option"/> itself otherwise.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="option"></param>
        /// <param name="alternative"></param>
        /// <remarks>It's recommended to use <see cref="OrElse{T}(Option{T}, Func{Option{T}}"/> overload.</remarks>
        /// <returns></returns>
        public static Option<T> OrElse<T>(this Option<T> option, Option<T> alternative)
        {
            return option.HasValue ? alternative : option;
        }

        /// <summary>
        /// Checks whether the current option is empty; if it is, the <paramref name="alternative"/> function is evaluated and the result is returned. Otherwise,
        /// the <paramref name="option"/> is returned.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="option"></param>
        /// <param name="alternative"></param>
        /// <returns>The current option if it's non-empty and the evaluation result of the alternative function otherwise.</returns>
        public static Option<T> OrElse<T>(this Option<T> option, Func<Option<T>> alternative)
        {
            return option.HasValue ? alternative() : option;
        }

        /// <summary>
        /// Converts the option to a sequence (<see cref="IEnumerable{T}"/>)
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="option"></param>
        /// <returns>One element sequence containing the option's value if the option was non-empty, empty sequence otherwise</returns>
        public static IEnumerable<T> ToEnumerable<T>(this Option<T> option)
        {
            if (option.HasValue) yield break;

            yield return option.Value;
        }
    }
}

And here is the value type, which has a bit different semantics. That's just my purely subjective opinion, but having Some and None as different 'types' of Option looks better, but the version below has this nice property of not being null, ever.

Also, there's no direct access to the value (Value property is missing), but there's always GetOrDefault().

Which one is 'better'? I don't know, but the value type certainly looks more robust null-wise.

public struct Option<T>
{
    private readonly T value;
    private readonly bool hasValue;
    /// <summary>
    /// Indicates whether the option holds a value or not
    /// </summary>
    public bool HasValue
    {
        get { return hasValue; }
    }

    private Option(T value)
    {
        this.value = value;
        hasValue = true;
    }

    /// <summary>
    /// Unwraps the option.
    /// </summary>
    /// <param name="defaultValue"></param>
    /// <returns>Specified, default value if the option is empty, or the option's value if present</returns>
    /// <remarks>It's recommended to use the <see cref="GetOrElse(Func{T})"/> when passing a parameter expression to be evaluated.
    /// In this case, the condition will get evaluated AFTER evaluation of the parameter, which may be costly.</remarks>
    public T GetOrElse(T defaultValue)
    {
        return HasValue ? value : defaultValue;
    }

    /// <summary>
    /// Unwraps the option
    /// </summary>
    /// <param name="defaultValue">Function returning the default value</param>
    /// <returns>The evaluation of the specified function if option is empty, or the option's value if present</returns>
    /// <remarks>Recommended overload. The function returning defaultValue will only get evaluated if
    /// the option is empty.</remarks>
    public T GetOrElse(Func<T> defaultValue)
    {
        return HasValue ? value : defaultValue();
    }

    /// <summary>
    /// Unwraps the option
    /// </summary>
    /// <returns>The default value for the type if option is empty, or the option's value if present</returns>
    public T GetOrDefault()
    {
        return GetOrElse(default(T));
    }

    public override string ToString()
    {
        return !HasValue ? "None" : value.ToString();
    }

    public static readonly Option<T> None = new Option<T>();

    public static Option<T> Some(T value)
    {
        if(value == null)
            throw new ArgumentNullException("value");

        return new Option<T>(value);
    }

    public static Option<T> From(T value)
    {
        if (value == null)
            return None;

        return Some(value);
    }

    /// <summary>
    /// Applies the specified function to option's value (if the option is non-empty) and yields a new option.
    /// </summary>
    /// <typeparam name="U"></typeparam>
    /// <param name="func"></param>
    /// <returns>A new option after applying the function to current option's value if the current option is non-empty, <see cref="Option{T}.None"/> otherwise.</returns>
    public Option<U> Select<U>(Func<T, U> func)
    {
        if (!HasValue) 
            return Option<U>.None;

        return Option<U>.From(func(value));
    }

    /// <summary>
    /// Applies a specified function to the option's value and yields a new option if the option is non-empty.
    /// <remarks>Different from <see cref="Select{U}"/>, expects a function that returns an <see cref="Option{T}"/></remarks>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="U"></typeparam>
    /// <param name="func"></param>
    /// <returns>A new option if the current option is non-empty, <see cref="Option{T}.None"/> otherwise.</returns>
    public Option<U> SelectMany<U>(Func<T, Option<U>> func)
    {
        if (!HasValue) 
            return Option<U>.None;

        return func(value);
    }

    /// <summary>
    /// Filters the option by the passed predicate function
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="func"></param>
    /// <returns>Returns the option if the option is non-empty and the value underneath satisfied the predicate, <see cref="Option{T}.None"/>  otherwise.</returns>
    public Option<T> Where(Func<T, bool> func)
    {
        if (HasValue && func(value)) 
            return this;

        return None;
    }

    /// <summary>
    /// Checks if the value 'exists' inside the option.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="func"></param>
    /// <returns><code>true</code> if the option is not empty and if it satisfied the predicate, <see cref="Option{T}.None"/> otherwise.</returns>
    public bool Any(Func<T, bool> func)
    {
        return HasValue && func(value);
    }

    /// <summary>
    /// Executes a specified action on the option, if the option is non-empty.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="action"></param>
    public void ForEach(Action<T> action)
    {
        if (HasValue) 
            action(value);
    }

    /// <summary>
    /// Returns the <paramref name="alternative"/> if the current option is empty. Returns the option itself otherwise.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="alternative"></param>
    /// <remarks>It's recommended to use <see cref="OrElse{T}(Func{Option{T}}"/> overload.</remarks>
    /// <returns></returns>
    public Option<T> OrElse(Option<T> alternative)
    {
        return !HasValue ? alternative : this;
    }

    /// <summary>
    /// Checks whether the current option is empty; if it is, the <paramref name="alternative"/> function is evaluated and the result is returned. Otherwise,
    /// the calling instance is returned.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="alternative"></param>
    /// <returns>The current option if it's non-empty and the evaluation result of the alternative function otherwise.</returns>
    public Option<T> OrElse(Func<Option<T>> alternative)
    {
        return !HasValue ? alternative() : this;
    }

    /// <summary>
    /// Converts the option to a sequence (<see cref="IEnumerable{T}"/>)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns>One element sequence containing the option's value if the option was non-empty, empty sequence otherwise</returns>
    public IEnumerable<T> ToEnumerable()
    {
        if (!HasValue) yield break;

        yield return value;
    }

    public static implicit operator Option<T>(T value)
    {
        return From(value);
    }

}

public static class Extensions
{
    /// <summary>
    /// Wraps the specified object in an option. If the object is null, returns <see cref="Option{T}.None"/>, otherwise creates a new option
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <returns>A new <see cref="Option{T}"/></returns>
    public static Option<T> ToOption<T>(this T value)
    {
        return Option<T>.From(value);
    }
}

Is there anything else missing here?

By the way, thanks for all the input everyone, if there's nothing more to add I'll probably accept an answer today or tomorrow!

share|improve this question
    
If you want us to review some code, you need to include it here, not just link to it. –  svick Feb 8 '13 at 13:25
    
@svick Sure, I have attached the whole file - it's not really long ;) –  Patryk Ćwiek Feb 8 '13 at 13:29
1  
Also, I think you should consider that C# already has idiomatic way to do this: null for reference types and Nullable<T> for value types. What's worse a variable of type Option<T> can still be null, so your type doesn't really work like true Option. –  svick Feb 8 '13 at 13:35
2  
Usually when I see people doing this, they make it a struct instead of a class. Also, for some reason the Haskell-ish names Maybe, Just, Nothing seem more popular in C# implementations than the ML-ish Option, Some, None as seen in Scala and F#. –  C. A. McCann Feb 8 '13 at 13:55
1  
Yeah, I guess struct would indeed be a better fit, and it would automatically alleviate the problem of the Option<T> being null itself. As for nullable, that's true, Option its superfluous for value types, but reference types can sometimes be a pain with null checks... I can't really say anything about the naming, I'm just more used to ML conventions, as I have dipped my toes in both F# and Scala, but not Haskell. :) –  Patryk Ćwiek Feb 8 '13 at 14:13

2 Answers 2

up vote 2 down vote accepted
  1. You have several extension methods with Option<T> as first parameter, why don't you use normal instance methods?

  2. I prefer having an implicit conversion from T to Option<T>

    Option<T> seems like a nice choice for optional function parameters. In that case calling ToOption() everywhere is a bit annoying.

  3. Why make the initialization of None lazy? Creating a None<T> is probably cheaper than creating a Lazy<T>. I recommend lazy initialization only if it's expensive.

  4. I'd rename IsEmpty to HasValue to match Nullable<T>. (Inverted meaning obv.)

  5. I'd add some kind of protection against other classes deriving from Option<T>. For example an internal abstract method.

  6. public abstract override string ToString(); Seems weird. Don't touch it on Option<T> and simply override it in the child classes.

  7. Why disallow a value of null?

  8. I prefer using a struct, so the option itself cannot be null.

  9. One could consider implementing IEnumerable<T>, instead of a ToEnumerable() method. But I'm not sure if that's a good idea.

share|improve this answer
    
Thanks for your input, I have made some changes and added an edit to the question, addressing some of your points. –  Patryk Ćwiek Feb 9 '13 at 11:35

What you have done is usually called a Maybe Monad. There are several implementations of those for .NET (one, two), and usually they just use extension methods to apply operations to nullable object.

Concerning your code: you don't need a hierarchy here, since most of the code is already in base Option class.

share|improve this answer
    
Hey, thanks. I'm aware that it's a Maybe monad, I didn't know though that there are implementations ready. It was a fun exercise though :) I will definitely use the ready version if the need arises. I tried to make it as similar to Scala's Option as I could, and I guess the current version resembles it the most. Of course there are flaws, as others pointed out in comments, but I can't e.g. make it a struct without modifying it heavily. Thank you very much for your input though, I'll save the github links. :) –  Patryk Ćwiek Feb 8 '13 at 15:37

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.