Специфичная для класса функция в C# generics - небольшой мод для шаблона UWP Observable

Я создаю приложение с использованием новейшего подхода UWP (и Windows Template Studio). Есть умный класс под названием "Observable". Так что это просто фон. Я хочу изменить условие в строке 13, чтобы незначительные изменения для двойников не помечали изменение свойства. Итак, я дополнил эту строку-13 и создал новую функцию с именем NeglitableChange...

    protected void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
    {
        if ((typeof(T) == typeof(double) && NegligibleChange(storage, value)) || Equals(storage, value))
        {
            return;
        }
        storage = value;
        OnPropertyChanged(propertyName);
    }

    private bool NegligibleChange(double  x, double y)
    {
        return Math.Abs(x - y) <= 1e-10;
    }

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

Это не работает, потому что он говорит "не может преобразовать T в удвоение". Есть ли способ это исправить?

2 ответа

Решение

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

Тем не менее, во-первых, фактический вопрос так и не получил ответа, и, во-вторых, есть некоторые оговорки в отношении этого подхода, которые следует рассмотреть.

Это не работает, потому что он говорит "не может преобразовать T в удвоение". Есть ли способ это исправить?

Да, есть несколько способов сделать это, и некоторые из них лучше, чем другие.

Во-первых, это вообще плохой способ проверки типа.

typeof(T) == typeof(double)

У вас есть два Т в руке, поэтому вместо этого вы должны сделать это:

protected void Set<T>(ref T storage, T value, blah blah blah)
{
    if (Equals(storage, value))
        return;
    double? oldValue = storage as double?;
    double? newValue = value as double?;
    if (oldValue != null && newValue != null && Negligible(oldValue.Value, newValue.Value))
      return;
    ...

Обратите внимание, что плохой способ сделать это:

protected void Set<T>(ref T storage, T value, blah blah blah)
{
    if (Equals(storage, value))
        return;
    if (storage is double && value is double) 
    {
      double oldValue = (double)(object)storage;
      double newValue = (double)(object)value;
      if (Negligible(...

Потому что это требует штрафа в боксе; джиттер не обязательно достаточно умен, чтобы оптимизировать двойной -> объект -> двойной шаг, что дорого.


Хотя, как я уже сказал, в целом, если это возможно, рекомендуется специализироваться, рассмотрим следующую ситуацию. Если вы специализируетесь и создаете одну версию, которая делает удвоение, и одну, которая делает все остальное, то:

Set(ref someDouble, 1.23)

назову двойную версию, но

Set<double>(ref someDouble, 1.23)

по-прежнему будет вызывать универсальную версию. C# предпочитает неуниверсальную версию универсальной версии, но если вы явно запросите универсальную версию, вы получите ее.

Точно так же, если вы вызываетесь из общего контекста:

class C<T> 
{
  T storage;
  void Frob(Blah blah, T value) {
     blah.Set<T>(ref storage, value);
  }

затем C<double> не вызывает вашу специальную версию; опять же это вызывает Set<double> как просили.

Так что будь осторожен.

Хорошо, у меня есть ответ на мою проблему. Знаете, дженерики сложно понять. Пожалуйста, прокомментируйте, если следующее не способ решить эту проблему. Необходимо добавить неуниверсальную версию функции Set с входными сигнатурами типа double. Таким образом, нет никаких дополнительных условий во время выполнения.

    protected void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
    {
        if (Equals(storage, value))
        {
            return;
        }
        storage = value;
        OnPropertyChanged(propertyName);
    }
    protected void Set(ref double storage, double value, [CallerMemberName]string propertyName = null)
    {
        if (NegligibleChange(storage, value))
        {
            return;
        }
        storage = value;
        OnPropertyChanged(propertyName);
    }

    private bool NegligibleChange(double  x, double y)
    {
        return Math.Abs(x - y) <= 1e-10;
    }

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
Другие вопросы по тегам