Могу ли я вернуть другой тип из функции в универсальном классе в зависимости от того, является ли универсальный тип, используемый для класса, значением или ссылочным типом? - PullRequest
3 голосов
/ 25 июня 2019

Я хочу вернуть Nullable<Value> из функции в универсальном классе, если Value является типом значения, и просто 'Value', если значение уже является ссылочным типом.

Я пытался работать с предложениями where, чтобы добиться этого, но я не могу использовать класс where для неуниверсальной функции, даже в универсальном классе.

    public class HashTable<Key, Value>
    {
        private List<ValueSlot> valueSlots = new List<ValueSlot>();

        private struct ValueSlot
        {
            public bool hasValue;
            public List<KeyValuePair<Key, Value>> values;
        }

        //...

        public Nullable<Value> TryGetValue(Key key) where Value : struct
        {
            (bool result, Value value) = TryGetValue(valueSlots, key);

            if (result == true)
            {
                Nullable<Value> myVal = value;
                return myVal;
            }
            else
            {
                return null;
            }
        }

        public Value TryGetValue(Key key) where Value : class
        {
            (bool result, Value value) = TryGetValue(valueSlots, key);

            return result == true ? value : (Value)null;
        }

        private (bool, Value) TryGetValue(List<ValueSlot> valueSlots, Key key)
        {
            //...
        }

        //...
    }

К сожалению, это не компилирует, генерирует кучу ошибок. Я бы не стал возвращать кортежи из класса. Есть ли простой способ заставить этот подход работать, который мне не хватает? Я пытался заставить это работать почти неделю ...

Ответы [ 4 ]

2 голосов
/ 25 июня 2019

Я бы предложил использовать Maybe<T>, чтобы вы могли сделать это:

public class HashTable<Key, Value>
{
    public Maybe<Value> TryGetValue(Key key)
    {
        (bool result, Value value) = TryGetValue(valueSlots, key);

        if (result == true)
        {
            return new Maybe<Value>(value); // or value.ToMaybe() // with the extension method
        }
        else
        {
            return Maybe<Value>.Nothing;
        }
    }
}

Это работает независимо от типа Value.

Вот реализация, которую я использую:

public static class MaybeEx
{
    public static Maybe<T> ToMaybe<T>(this T value)
    {
        return new Maybe<T>(value);
    }

    public static T GetValue<T>(this Maybe<T> m, T @default) => m.HasValue ? m.Value : @default;
    public static T GetValue<T>(this Maybe<T> m, Func<T> @default) => m.HasValue ? m.Value : @default();

    public static Maybe<U> Select<T, U>(this Maybe<T> m, Func<T, U> k)
    {
        return m.SelectMany(t => k(t).ToMaybe());
    }

    public static Maybe<U> SelectMany<T, U>(this Maybe<T> m, Func<T, Maybe<U>> k)
    {
        if (!m.HasValue)
        {
            return Maybe<U>.Nothing;
        }
        return k(m.Value);
    }

    public static Maybe<V> SelectMany<T, U, V>(this Maybe<T> @this, Func<T, Maybe<U>> k, Func<T, U, V> s)
    {
        return @this.SelectMany(x => k(x).SelectMany(y => s(x, y).ToMaybe()));
    }
}

public class Maybe<T>
{
    public class MissingValueException : Exception { }

    public readonly static Maybe<T> Nothing = new Maybe<T>();

    private T _value;
    public T Value
    {
        get
        {
            if (!this.HasValue)
            {
                throw new MissingValueException();
            }
            return _value;
        }
        private set
        {
            _value = value;
        }
    }
    public bool HasValue { get; private set; }

    public Maybe()
    {
        HasValue = false;
    }

    public Maybe(T value)
    {
        Value = value;
        HasValue = true;
    }

    public T ValueOrDefault(T @default) => this.HasValue ? this.Value : @default;
    public T ValueOrDefault(Func<T> @default) => this.HasValue ? this.Value : @default();

    public static implicit operator Maybe<T>(T v)
    {
        return v.ToMaybe();
    }

    public override string ToString()
    {
        return this.HasValue ? this.Value.ToString() : "<null>";
    }

    public override bool Equals(object obj)
    {
        if (obj is Maybe<T>)
            return Equals((Maybe<T>)obj);
        return false;
    }

    public bool Equals(Maybe<T> obj)
    {
        if (obj == null) return false;
        if (!EqualityComparer<T>.Default.Equals(_value, obj._value)) return false;
        if (!EqualityComparer<bool>.Default.Equals(this.HasValue, obj.HasValue)) return false;
        return true;
    }

    public override int GetHashCode()
    {
        int hash = 0;
        hash ^= EqualityComparer<T>.Default.GetHashCode(_value);
        hash ^= EqualityComparer<bool>.Default.GetHashCode(this.HasValue);
        return hash;
    }

    public static bool operator ==(Maybe<T> left, Maybe<T> right)
    {
        if (object.ReferenceEquals(left, null))
        {
            return object.ReferenceEquals(right, null);
        }

        return left.Equals(right);
    }

    public static bool operator !=(Maybe<T> left, Maybe<T> right)
    {
        return !(left == right);
    }
}
2 голосов
/ 25 июня 2019

Полиморфизм по типу возврата еще не поддерживается C #. Ограничение универсального типа является подсказкой для компилятора и не делает сигнатуры методов другими.

1 голос
/ 25 июня 2019

Я верю, что вы ищете это может быть монадой.Это требует некоторой работы.Но это того стоит imho.Реализация Maybe Monad для C # хорошо описана здесь .

0 голосов
/ 25 июня 2019

Чтобы сделать эту работу, нужно пойти на некоторые компромиссы:

public V? GetNullable<V>(Key key) where V : struct, Value
{
    (bool result, Value value) = TryGetValue(valueSlots, key);
    return result ? (V)value : (V?)null;
}

public V GetValueOrNull<V>(Key key) where V : class, Value
{
    (bool result, Value value) = TryGetValue(valueSlots, key);
    return result == true ? (V)value : null;
}

Вы должны давать разные имена двум методам и указывать тип в качестве параметра при каждом их вызове:

var h1 = new HashTable<int, DateTime>(); // Value Type
DateTime? date = h1.GetNullable<DateTime>(1);

var h2 = new HashTable<int, Exception>(); // Reference Type
Exception ex = h2.GetValueOrNull<Exception>(1);

Я думаю, что это побеждает цель.

...