Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid calling RaisePropertyChanged in every setter

I want to get rid of the space consuming and repetitive RaisePropertyChanged-Properties on my model classes. I want my model class...

public class ProductWorkItem : NotificationObject
{
    private string name;
    public string Name
    {
        get { return name; }
        set { 
            if (value == name) return; 
            name = value; RaisePropertyChanged(() => Name); 
        }
    }
    private string description;
    public string Description
    {
        get { return description; }
        set { 
            if (value == description) return; 
            description = value; RaisePropertyChanged(() => Description); 
        }
    }
    private string brand;
    public string Brand
    {
        get { return brand; }
        set { 
            if (value == brand) return; 
            brand = value; RaisePropertyChanged(() => Brand); 
        }
    }
}

...to look as simple as this again: (but notify the view when a property changes)

public class ProductWorkItem
{
    public string Name{ get; set; }
    public string Description{ get; set; }
    public string Brand{ get; set; }
}

Could this be achieved with some sort of proxy class?

I want to avoid writing a proxy for every single model class.

like image 668
tweakch Avatar asked Dec 01 '12 21:12

tweakch


3 Answers

I know of no simple and maintainable approach to this in "vanilla" C#, but you can achieve this with aspects. I have used PostSharp for this, which has a disadvantage of being a paid 3rd party product, but has a free version, where you can do this as well. PostSharp leverages the advantages of attributes like target specifying, inheritance etc. and extends them to aspects.

You can then define a LocationInterceptionAspect, which overrides OnSetValue method to call your RaisePropertyChanged delegate. Then you can use autogenerated properties decorated with your aspect attribute.

Paid version of PostSharp allows you to do this on class level, so you would only need one attribute (or none, if you decorate your base class and define the attribute as inheritable). This is described on the PostSharp site as a use case of InstanceLevelAspect

like image 156
Honza Brestan Avatar answered Oct 15 '22 00:10

Honza Brestan


I came along the NotifyPropertyWeaver extension and haved used it on a regular basis since then. It's a Visual Studio extension, which implements the always same INPC stuff for you, before the code gets compiled. You don't notice anything of that.

You need to install the extension, and your model then needs to look like this:

public class ProductWorkItem : INotifyPropertyChanged
{
    public string Name{ get; set; }
    public string Description{ get; set; }
    public string Brand{ get; set; }

    public event PropertyChangedEventHandler PropertyChanged;
}

The extension than adds all the rest for you. What I like about that approach, is that your class still "officially" implements the INPC interface and you can use it in non-WPF contexts as well (as INPC is not at all just a WPF thing), but still don't have to litter you classes with all that stuff. It raises notifications for readonly properties that depend on a property.

Of course, it's a bit fake, as it just automizes the writing and doesn't change anything about the underlying concept at all. But maybe it's a compromise...

Here is more information: Link

like image 43
Marc Avatar answered Oct 15 '22 01:10

Marc


We can avoid repetitive code of writing RaisePropertyChanged on each property setter in WPF.

Use free version of Postsharp.

By using following code we can bind only Virtual property to view.

namespace Test
{
[Serializable]
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)]
public sealed class RaisePropertyChangedAttribute : MethodInterceptionAspect
{
    private string propertyName;

    /// <summary>
    /// Compiles the time validate.
    /// </summary>
    /// <param name="method">The method.</param>
    public override bool CompileTimeValidate(MethodBase method)
    {
        return IsPropertySetter(method) && !method.IsAbstract && IsVirtualProperty(method);
    }

    /// <summary>
    /// Method invoked at build time to initialize the instance fields of the current aspect. This method is invoked
    /// before any other build-time method.
    /// </summary>
    /// <param name="method">Method to which the current aspect is applied</param>
    /// <param name="aspectInfo">Reserved for future usage.</param>
    public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
    {
        base.CompileTimeInitialize(method, aspectInfo);
        propertyName = GetPropertyName(method);
    }

    /// <summary>
    /// Determines whether [is virtual property] [the specified method].
    /// </summary>
    /// <param name="method">The method.</param>
    /// <returns>
    ///   <c>true</c> if [is virtual property] [the specified method]; otherwise, <c>false</c>.
    /// </returns>
    private static bool IsVirtualProperty(MethodBase method)
    {
        if (method.IsVirtual)
        {
            return true;
        }

        var getMethodName = method.Name.Replace("set_", "get_");
        var getMethod = method.DeclaringType.GetMethod(getMethodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

        return getMethod != null && getMethod.IsVirtual;
    }

    private static string GetPropertyName(MethodBase method)
    {
        return method.Name.Replace("set_", string.Empty);
    }

    /// <summary>
    /// Determines whether [is property setter] [the specified method].
    /// </summary>
    /// <param name="method">The method.</param>
    /// <returns>
    /// <c>true</c> if [is property setter] [the specified method]; otherwise, <c>false</c>.
    /// </returns>
    private static bool IsPropertySetter(MethodBase method)
    {
        return method.Name.StartsWith("set_", StringComparison.OrdinalIgnoreCase);
    }

    /// <summary>
    /// Method invoked <i>instead</i> of the method to which the aspect has been applied.
    /// </summary>
    /// <param name="args">Advice arguments.</param>
    public override void OnInvoke(MethodInterceptionArgs args)
    {
        var arg = args as MethodInterceptionArgsImpl;

        if ((arg != null) && (arg.TypedBinding == null))
        {
            return;
        }

        // Note ViewModelBase is base class for ViewModel
        var target = args.Instance as ViewModelBase;

        args.Proceed();

        if (target != null)
        {
            target.OnPropertyChanged(propertyName);                    
        }
    }
}
}
like image 2
Amit Avatar answered Oct 15 '22 01:10

Amit