Martin Dybal

Vývojářský blog Martina Dybala

Podle kategorie

Aspektově orientované programování – INotifyPropertyChanged

1. díl - Aspektově orientované programování – INotifyPropertyChanged

Martin Dybal       15. 1. 2016       PostSharp a AOP       3572 zobrazení

Dnes si ukážeme jak snadně naimplementovat INotifyPropertyChanged.

Všichni co dělali z wpf to určitě znají dlouhý a špatně přehledný kód modelů, který vypadá nějak takto:

class User : INotifyPropertyChanged
{
    private string firstName;
    private string lastName;
    private string phone;

    public event PropertyChangedEventHandler PropertyChanged;

    public string FirstName
    {
        get { return firstName; }
        set
        {
            if (value != firstName)
            {
                firstName = value;
                OnPropertyChanged();
                OnPropertyChanged("FullName");
            }
        }
    }

    public string LastName
    {
        get { return lastName; }
        set
        {
            if (value != lastName)
            {
                lastName = value;
                OnPropertyChanged();
                OnPropertyChanged("FullName");
            }
        }
    }

    public string FullName
    {
        get { return string.Format("{0} {1}", this.FirstName, this.LastName); }
    }

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Rozhraní INotifyPropertyChanged se dá velmi pohodlně naimplementovat pomocí PostSharpu.

Implementace INotifyPropertyChanged

Vytvoříme třídu s požadovanými vlastnostmi:

internal class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string FullName
    {
        get { return string.Format("{0} {1}", this.FirstName, this.LastName); }
    }
}

Obdobně jako v případě přidání logování klikneme na název třídy a rozbalíme nabídku možností (CTRL+.). Zvolíme možnost “Implement INotifyPropertyChanged”

image 

Při první implementaci INotifyPropertyChanged v projektu potřebuje PostSharp stáhnout balíček PostSharp.Patterns.Model. O tom nás informuje jednoduchým dialogem.

image

image

image

Ke třídě se připojí atribut NotifyPropertyChanged:

[NotifyPropertyChanged]
internal class User
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string FullName
    {
        get { return string.Format("{0} {1}", this.FirstName, this.LastName); }
    }
}

No a tím máme hotovo. Ovšem má to malou záludnost. Wpf binding funguje správně, ale nemůžeme se navázat na event PropertyChanged ze C# kódu, jednoduše proto, že ho tam v době kompilace nemáme. PostSharp ho tam doplní až po kompilaci. Pokud potřebujeme někde v kódu reagovat na událost PropertyChanged, musíme použít přetypování na INotifyPropertyChanged.

User user = new User { FirstName = "John", LastName = "Doe" };
((INotifyPropertyChanged)user).PropertyChanged += user_PropertyChanged;

To se ovšem nezdá Visual Studiu a reaguje hláškou:

image

PostSharp má pro podobné situace metodu Post.Cast<SourceType, TargetType>. Hlavní výhodou použití téhle metody o proti klasickému přetypování je, že v případě kdy nebude přetypování možné, vyhodí PostSharp chybu při kompilaci, kdežto klasické přetypování jí vyhodí až za běhu programu.

User user = new User { FirstName = "John", LastName = "Doe" };
Post.Cast<User, INotifyPropertyChanged>(user).PropertyChanged += user_PropertyChanged;

Post kompilace

Už jsme si ukázali jak pomocí PostSharpu logovat, jak naimplementovat INotifyPropertyChanged, ale neukázali jsme si jak funguje. Jak už jsem zmínil výše, aspekty se připojují až po kompilaci. Projekt se nejdříve zkompiluje, postsharp post processor pak vezme vygenerovanou assembly, pomocí MSIL injection připojí aspekty a vygeneruje novou výslednou assembly.

image

Pojďme se podívat, co generuje PostSharp. Použiju jednoduchý aspekt podobný jako byl v prvním dílu.

 
[Serializable]
public sealed class TraceInfoAttribute : OnMethodBoundaryAspect
{
    public override void OnEntry(MethodExecutionArgs args)
    {
        Trace.WriteLine(string.Format("Entering {0} with parametr {1}", GetMethodFullName(args), ArgumentsToString(args)));
    }

    public override void OnException(MethodExecutionArgs args)
    {
        Trace.WriteLine(string.Format("Exception {0} on method {1}", args.Exception, GetMethodFullName(args)));
    }

    public override void OnSuccess(MethodExecutionArgs args)
    {
        Trace.WriteLine(string.Format("Success {0}", GetMethodFullName(args)));
    }

    public override void OnExit(MethodExecutionArgs args)
    {
        Trace.WriteLine(string.Format("Leaving {0}", GetMethodFullName(args)));
    }

    private static string ArgumentsToString(MethodExecutionArgs args)
    {
        ParameterInfo[] methodParams = args.Method.GetParameters();
        List paramsList = new List();
        for (int x = 0; x < methodParams.Count(); x++)
        {
            paramsList.Add(string.Format("{0}: {1} = {2}",
                methodParams[x].ParameterType.FullName,
                methodParams[x].Name,
                args.Arguments[x]));
        }
        return string.Join(", ", paramsList);
    }

    private static string GetMethodFullName(MethodExecutionArgs args)
    {
        return string.Format("{0}.{1}.{2}",
            args.Method.DeclaringType.Namespace,
            args.Method.DeclaringType.Name,
            args.Method.Name);
    }
}
[TraceInfo]
private static void SayHello(string who, int howManyTimes)
{
    for (int i = 0; i < howManyTimes; i++)
    {    
        Console.WriteLine("Hello, {0}.", who);
    }
}

Když metodu SayHello, k níž je aspekt připojen, dekompilujem Reflectorem nebo jiným dekompilátorem, uvidíme:

private static void SayHello(string who, int howManyTimes)
{
    Arguments<string, int> arguments = new Arguments<string, int> {
        Arg0 = who,
        Arg1 = howManyTimes
    };
    MethodExecutionArgs args = new MethodExecutionArgs(null, arguments) {
        Method = <>z__a_1._2
    };
    <>z__a_1.a0.OnEntry(args);
    try
    {
        for (int i = 0; i < howManyTimes; i++)
        {
            Console.WriteLine("Hello, {0}.", who);
        }
        <>z__a_1.a0.OnSuccess(args);
    }
    catch (Exception exception)
    {
        args.Exception = exception;
        <>z__a_1.a0.OnException(args);
        throw;
    }
    finally
    {
        <>z__a_1.a0.OnExit(args);
    }
}

Pojďme si kód trošku rozebrat. Proměnná methodExecutionArgs obsahuje informace o metodě. Konstruktoru se předává null, protože první parametr je instance objektu na kterém je metoda vyvolána. Druhým jsou parametry metody, pokud je metoda bezparametrická a nebo aspekt paremetry nepotřebuje, bude i druhý paramter null. Do vlastnosti Method proměnné methodExecutionArgs se vloží informace o metodě jako je jmený prostor, název atd... Vlastnost je typu MethodExecutionArgs.

<>z__a_1 je třída, ve které si PostSharp udržuje informace o třídách a metodách a instance atributů. Pro každou třídu a každou metodu je zde statická properity s informacemi. Například <>z__a_1._2 jsou informace o metodě SayHello a <>z__a_1.a0 je instance TraceInfoAttribute použitá v metodě SayHello.

[CompilerGenerated, DebuggerNonUserCode]
internal sealed class <>z__a_1
{
    // Fields
    internal static MethodBase _2;
    internal static readonly TraceInfoAttribute a0;

    // Methods
    [CompilerGenerated]
    static <>z__a_1();
}

Optimalizace aspektů

PostSharp je naštěstí dost chytrý na to, aby dělal jen to co je opravdu potřeba. Pokud zjednodušíme aspekt.

[Serializable]
public sealed class TraceInfoAttribute : OnMethodBoundaryAspect
{
    public override void OnEntry(MethodExecutionArgs args)
    {
        Trace.WriteLine("Entry");
    }
}

Bude kód metody SayHello takový to:

private static void SayHello(string who, int howManyTimes)
{
    <>z__a_1.a0.OnEntry(null);
    for (int i = 0; i < howManyTimes; i++)
    {
        Console.WriteLine("Hello, {0}.", who);
    }
}

Všiměte si že zmizela počáteční inicializace Arguments i MethodExecutionArgs a blok try catch, protože nejsou pro aktuální aspekt potřeba. Bohužel PostSharp provádí optimalizaci až od verze Professional. Ve verzi express se musíme spokojit s voláním prázdných metod.

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

Mohlo by vás také zajímat

 

Nový příspěvek

 

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback