Может быть, класс и дополнительные параметры
У меня есть реализация класса Maybe / Option в C#. Базовая реализация
public delegate Maybe<TOutput> Converter<in TInput, out TOutput>(TInput input);
public delegate TOutput ElseDelegate<out TOutput>();
public delegate Maybe<TOutput> ElseDelegate2<out TOutput>();
public interface Maybe<out TResult> : IEnumerable<TResult>
{
Maybe<B> Bind<B>(Converter<TResult, B> f);
TResult Value();
bool IsSome();
}
public static class Maybe
{
public static Maybe<T> None<T>()
{
return new None<T>();
}
}
public interface INone<out TResult> : Maybe<TResult>
{
}
public interface ISome<out TResult> : Maybe<TResult>
{
}
public struct None<TResult> : INone<TResult>
{
public IEnumerator<TResult> GetEnumerator()
{ yield break; }
IEnumerator IEnumerable.GetEnumerator()
{ yield break; }
public bool IsSome() { return false; }
public Maybe<TOutput> Bind<TOutput>(Converter<TResult, TOutput> f)
{
return new None<TOutput>();
}
public TResult Value()
{
throw new IndexOutOfRangeException("None has no value");
}
}
public struct Some<TResult> : Maybe<TResult>
{
private TResult _Value;
public Some(TResult value)
{
_Value = value;
}
public IEnumerator<TResult> GetEnumerator()
{ yield return _Value; }
IEnumerator IEnumerable.GetEnumerator()
{ yield return _Value; }
public bool IsSome() { return true; }
public Maybe<TOutput> Bind<TOutput>(Converter<TResult, TOutput> f)
{
return f(_Value);
}
public TResult Value()
{
return this._Value;
}
}
#endregion
с кучей методов расширения я здесь не включил. Это все отлично работает. Однако стандартный шаблон, который я хотел бы реализовать, приведен ниже, используя Maybe для реализации необязательных параметров по умолчанию, как в F#
void DoSomeCalc
( Maybe<double> x = Maybe.None<double>()
, Maybe<double> y = Maybe.None<double>()
)
{
this.X = x.Else( ()=> CalculateDefaultX() );
this.Y = y.Else( ()=> CalculateDefaultY() );
}
так что я могу сделать
DoSomeCalc(x:10)
или же
DoSomeCalc(y:20)
где Else предоставляет значение, если None недоступен. Тем не менее, это все хорошо в теории, но необязательные параметры C# должны быть константами времени компиляции, которые полностью портят этот шаблон.
Кто-нибудь может предложить исправление, которое сохранит намерение шаблона, не вводя здесь nullables или null?
Могу ли я в любом случае создать постоянную времени компиляции для представления None, которая будет работать с моей приведенной выше реализацией Maybe?
2 ответа
Нет, ты ничего не можешь сделать здесь. Ваш тип параметра является ссылочным типом, что означает, что доступны только постоянные значения null
и строковые литералы. (Очевидно, строковые литералы бесполезны в вашем случае; я упоминаю их только как единственный вид константы ненулевого ссылочного типа.)
Одним из вариантов будет сделать Maybe<T>
структура вместо интерфейса со значением по умолчанию, равным "нет". Тогда это будет в основном так же, как Nullable<T>
но без ограничения, что T
должен был быть необнуляемым типом значения. Затем вы можете использовать:
void DoSomeCalc(Maybe<double> x = default(Maybe<double>),
Maybe<double> y = default(Maybe<double>))
Пример кода, показывающий все это:
using System;
struct Maybe<T>
{
private readonly bool hasValue;
public bool HasValue { get { return hasValue; } }
private readonly T value;
public T Value
{
get
{
if (!hasValue)
{
throw new InvalidOperationException();
}
return value;
}
}
public Maybe(T value)
{
this.hasValue = true;
this.value = value;
}
public static implicit operator Maybe<T>(T value)
{
return new Maybe<T>(value);
}
}
class Test
{
static void DoSomeCalc(Maybe<double> x = default(Maybe<double>),
Maybe<double> y = default(Maybe<double>))
{
Console.WriteLine(x.HasValue ? "x = " + x.Value : "No x");
Console.WriteLine(y.HasValue ? "y = " + y.Value : "No y");
}
static void Main()
{
Console.WriteLine("First call");
DoSomeCalc(x: 10);
Console.WriteLine("Second call");
DoSomeCalc(y: 20);
}
}
Очевидно, вы хотите добавить больше функциональности к Maybe<T>
например переопределение ToString
а также Equals
, но вы получите общее представление. Вы все еще можете иметь неуниверсальный Maybe
Конечно, класс с фабричными методами.
Вы можете использовать null для обозначения Maybe.None
double DoSomeCalc
( Maybe<double> x = null
, Maybe<double> y = null
)
{
x = x ?? Maybe.None<double>();
y = y ?? Maybe.None<double>();
this.X = x.Else( ()=> CalculateDefaultX() );
this.Y = y.Else( ()=> CalculateDefaultY() );
}
Это не идеально, так как вы должны документировать где-то в комментариях, что передача нуля означает "использовать определенное значение по умолчанию".