Специфичная для класса функция в 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));
}