1

I am working on a bindable base class that implements INotifyPropertyChanged and IDataErrorInfo so that I can write properties that bind to WPF with change notification and allow me to use DataAnnotations validation.

Kudos to this article: https://code.msdn.microsoft.com/windowsdesktop/Validation-in-MVVM-using-12dafef3 which I have copied from shamelessly

although the article is great, it doesn't take advantage of CallerMemberName so I'm trying to clean things up a bit.

One nifty thing the sample author did was to write SetValue and GetValue methods that store all private property values in a dictionary, which allows you to skip storing the property value in a private field in the class. The author used four functions to do this:

    /// <summary>
    /// Sets the value of a property.
    /// </summary>
    /// <typeparam name="T">The type of the property value.</typeparam>
    /// <param name="propertySelector">Expression tree contains the property definition.</param>
    /// <param name="value">The property value.</param>
    protected void SetValue<T>(Expression<Func<T>> propertySelector, T value)
    {
        string propertyName = GetPropertyName(propertySelector);

        SetValue<T>(propertyName, value);
    }

    /// <summary>
    /// Sets the value of a property.
    /// </summary>
    /// <typeparam name="T">The type of the property value.</typeparam>
    /// <param name="propertyName">The name of the property.</param>
    /// <param name="value">The property value.</param>
    protected void SetValue<T>(string propertyName, T value)
    {
        if (string.IsNullOrEmpty(propertyName))
        {
            throw new ArgumentException("Invalid property name", propertyName);
        }

        _values[propertyName] = value;
        NotifyPropertyChanged(propertyName);
    }

    /// <summary>
    /// Gets the value of a property.
    /// </summary>
    /// <typeparam name="T">The type of the property value.</typeparam>
    /// <param name="propertySelector">Expression tree contains the property definition.</param>
    /// <returns>The value of the property or default value if not exist.</returns>
    protected T GetValue<T>(Expression<Func<T>> propertySelector)
    {
        string propertyName = GetPropertyName(propertySelector);

        return GetValue<T>(propertyName);
    }

    /// <summary>
    /// Gets the value of a property.
    /// </summary>
    /// <typeparam name="T">The type of the property value.</typeparam>
    /// <param name="propertyName">The name of the property.</param>
    /// <returns>The value of the property or default value if not exist.</returns>
    protected T GetValue<T>(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName))
        {
            throw new ArgumentException("Invalid property name", propertyName);
        }

        object value;
        if (!_values.TryGetValue(propertyName, out value))
        {
            value = default(T);
            _values.Add(propertyName, value);
        }

        return (T)value;
    }

I have replaced these four functions with the following two:

    /// <summary>
    /// Sets the value of a property.
    /// </summary>
    /// <typeparam name="T">The type of the property value.</typeparam>
    /// <param name="propertyName">The name of the property.</param>
    /// <param name="value">The property value.</param>
    protected void SetValue<T>(T value, [CallerMemberName] string propertyName = "")
    {
        if (string.IsNullOrEmpty(propertyName))
        {
            throw new ArgumentException("Invalid property name", propertyName);
        }

        _values[propertyName] = value;
        NotifyPropertyChanged(propertyName);
    }

    /// <summary>
    /// Gets the value of a property.
    /// </summary>
    /// <typeparam name="T">The type of the property value.</typeparam>
    /// <param name="propertyName">The name of the property.</param>
    /// <returns>The value of the property or default value if not exist.</returns>
    protected T GetValue<T>([CallerMemberName] string propertyName = "")
    {
        if (string.IsNullOrEmpty(propertyName))
        {
            throw new ArgumentException("Invalid property name", propertyName);
        }

        object value;
        if (!_values.TryGetValue(propertyName, out value))
        {
            value = default(T);
            _values.Add(propertyName, value);
        }

        return (T)value;
    }

I think it's an improvement because it eliminates a few functions and simplifies calling the methods.

A property using the original functions is implemented as follows:

    [Range(1, 100, ErrorMessage = "Age should be between 1 to 100")]
    public int Age
    {
        get { return GetValue(() => Age); }
        set { SetValue(() => Age, value); }
    }

I would like to implement the same property in mine as shown below:

    [Range(1, 100, ErrorMessage = "Age should be between 1 to 100")]
    public int Age
    {
        get { return GetValue(); }
        set { SetValue(value); }
    }

The only problem is that GetValue gives me the error:

The type arguments for method ___.GetValue(string)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

So I have to implement it this way:

    [Range(1, 100, ErrorMessage = "Age should be between 1 to 100")]
    public int Age
    {
        get { return GetValue<int>(); }
        set { SetValue(value); }
    }

Any Ideas? I can't see why the original code could infer the type but my code can't.

H.B.
  • 166,899
  • 29
  • 327
  • 400
Eric
  • 1,392
  • 17
  • 37

1 Answers1

1

You can make GetValue's return type dynamic and it will be coerced back to the property type without error.

H.B.
  • 166,899
  • 29
  • 327
  • 400
  • is there a CallerMember attribute I could use? – Eric Aug 29 '16 at 20:51
  • @Eric: I don't know. Would just use dynamic. – H.B. Aug 29 '16 at 20:53
  • @Eric: The lack of something like that in [the listing here](https://msdn.microsoft.com/en-us/library/mt653988.aspx) makes it unlikely. – H.B. Aug 29 '16 at 20:54
  • @Eric: Also, even if there were such a thing it would not help you, because it just would supply you with run-time type information but what you need to overcome here is a static compile time type check. (You can get the member via reflection anyway, given that you already have the property name.) – H.B. Aug 29 '16 at 20:56
  • That works except for setting the default value of the property if it hasn't been set yet. – Eric Aug 29 '16 at 20:58
  • @Eric: Setting defaults like that is rather questionable anyway if you ask me. Would set the default in the constructor or via an attribute. – H.B. Aug 29 '16 at 20:59
  • @Eric: And as i said, you can get the type at run-time anyway and [create a default programmatically](http://stackoverflow.com/questions/325426/programmatic-equivalent-of-defaulttype). – H.B. Aug 29 '16 at 21:00
  • These are all good points but not a complete answer. Given a day or two I think I can come up with something more satisfying. – Eric Aug 29 '16 at 21:10
  • This would work. I didn't end up doing it because I felt it was cleaner just to include the type in the call, but it deserves to be recognized as a working answer given. – Eric Oct 06 '16 at 18:40