2

I'm building an app using the latest UWP (and Windows Template Studio) approach. There's a clever class called "Observable". So, that's just the background. I want to modify the condition on line-13 so that negligible changes for doubles won't flag a property change. So, I've augmented that line-13 and created a new function called NegligibleChange...

    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));
    }

This is not working because it says "cannot convert T to double". Is there a way to fix this?

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
mattica
  • 357
  • 3
  • 8
  • 1
    Cast the arguments.`NegligibleChange((double)storage, (double)value)` – fourwhey Sep 28 '18 at 00:09
  • Nope. That doesn't work for me. whether the cast is implicit or explicit, you get the same error: "Error CS0030 Cannot convert type 'T' to 'double'" – mattica Sep 28 '18 at 17:30

2 Answers2

3

The answer posted by the question asker is generally considered to be the correct approach. When you have a generic method that has to do something special for one particular type, it is no longer generic.

That said, first, the actual question never got answered, and second, there are some caveats about this approach to consider.

This is not working because it says "cannot convert T to double". Is there a way to fix this?

Yes, there are a couple of ways to do it, and some of them are better than others.

First off, this is in general a bad way to type test.

typeof(T) == typeof(double)

You have two Ts in hand, so instead you would do this:

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;
    ...

Note that a bad way to do it is:

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(...

Because this takes on a boxing penalty; the jitter is not necessarily smart enough to optimize away the double -> object -> double step, which is expensive.


Though as I said, in general its a good idea to specialize if possible, consider the following situation. If you specialize and make one version that does doubles and one that does everything else, then:

Set(ref someDouble, 1.23)

will call the double version, but

Set<double>(ref someDouble, 1.23)

will still call the generic version. C# prefers the non-generic version to the generic version, but if you explicitly ask for the generic version, you'll get it.

Similarly, if you are called from a generic context:

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

Then C<double> does not call your special version; again, this calls Set<double> as requested.

So be cautious.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • this is very informative and I will mark it as the answer. But, I think I will keep the approach I show below because it is compile-time fast and I'm not likely to create a hierarchy of functions that you mention at the end. I understand what you are saying in the difference of your two approaches, but I would have thought they compile to the same result - the first being just shorthand for the second. But, I trust you know better. By the way, the first is not compiling. It needs "?" after first double. (I can't add since revisions less than 6 characters is not allowed by stackoverflow. – mattica Sep 29 '18 at 19:48
1

Alright, I have an answer to my own problem. Generics are just hard to understand, you know. Please comment if the following is not the way to handle this problem. What is need is to add a non-generic version of the Set function that has the input signatures of doubles. This way there are no additional conditions during runtime.

    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));
    }
mattica
  • 357
  • 3
  • 8