Laden...

Intelligentes PropertySet

Erstellt von syn87 vor 14 Jahren Letzter Beitrag vor 14 Jahren 2.189 Views
S
syn87 Themenstarter:in
87 Beiträge seit 2008
vor 14 Jahren
Intelligentes PropertySet

Gutn Abend zusammen 😃

jeder kennt wahrscheinlich eine Methode ähnlich der Folgenden:


void SetProperty(string propertyName, ref int backing, int value)
{ ... }

Ich bin hier mal auf etwas gestoßen, was den MagicString kram umgeht:


 #region INotifyPropertyChanged Members   ...
2
3
4        [field:NonSerialized()]
5        public event PropertyChangedEventHandler PropertyChanged;
6
7        private void OnPropertyChanged(Expression<Func<object>> exp)
8        {
9            MemberExpression me = exp.Body as MemberExpression;
10
11            if (me == null)
12            {
13                me = ((UnaryExpression)exp.Body).Operand as MemberExpression;
14            }
15
16         
17            string propertyName = me.Member.Name;
18            OnPropertyChanged(propertyName);
19
20        }
21        private void OnPropertyChanged(string propName)
22        {
23            if (PropertyChanged != null)
24                PropertyChanged(this, new PropertyChangedEventArgs(propName));
25        }
26        #endregion

Vllt hat ja noch jemand Verbesserungsvorschläge.

LG

6.911 Beiträge seit 2009
vor 14 Jahren

Hallo,

sehr interessanter Ansatz.

Allerdings wäre der Link zum (vermutlichen) Original nicht schlecht:
How to use INotifyPropertyChanged, the type-safe way (no magic string)

mfG Gü

[Edit]
Hab anscheinend

Ich bin hier mal auf etwas gestoßen, was den MagicString kram umgeht:

falsch interpretiert.
[/Edit]

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

S
syn87 Themenstarter:in
87 Beiträge seit 2008
vor 14 Jahren

Das Original entstammt meinerseits von CodeKeep. Demnach auch nicht wirklich eine Referenz wert 😉

LG

446 Beiträge seit 2004
vor 14 Jahren

Ich finde die implementierung über Extenions schöner.

http://www.lieser-online.de/blog/?p=130

http://www.lieser-online.de/blog/?p=131

Schaut mal im IRC vorbei:
Server: https://libera.chat/ ##chsarp

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo zusammen,

ich weiß ja, dass Microsoft den Präprozessor aus guten Gründen so stark beschnitten hat, aber das wäre eigentlich ein klassischer Fall für das gute alte FUNCTION, dass durch den aktuellen Funktionsnamen ersetzt wird. Müsste es hier natürlich analog für Properties geben.

Das Problem bei den Lösung über Lambda-Audrücke ist doch, dass zwar geprüft wird, ob es den Property-Namen gibt, aber nicht, ob in der Property auch der Name dieser Property verwendet wird. Copy&Paste-Fehler bleiben also weiterhin unentdeckt.

In [Artikel] Attribute zur Prüfung von Properties verwenden verwende ich einen alternativen Weg, um sicherzustellen, dass der aktuelle Name der Property verwendet wird. Auch diese Lösung hat ihr Schönheitsfehler, aber zumindest muss man da den Namen der Property gar nicht mehr tippen, sondern er wird automatisch aus dem StackTrace ermittelt.

herbivore

6.911 Beiträge seit 2009
vor 14 Jahren

Hallo,

am idealsten wäre natürlich wenn der Compiler den Code für OnPropertyChanged genieren würde. Dann wäre das auch mit den "automatic properties" möglich.

Weiters es gäbe es keine Performanceeinbußen durch die Verwendung von Reflektion wenn herbivores alternativer Weg verwendet wird. Dieser der Vollständigkeit halber:


protected void OnPropertyChanged()
{
	MethodBase mb = new StackFrame(1).GetMethod();
	string propertyName = mb.Name.Substring(4);	// set_ auslassen.
	OnPropertyChanged(propertyName);
}

Könnte doch von Mircosoft berücksichtigt werden. Es gibt ja schon so vieles was [CompilerGenerated] ist.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo gfoidl,

wobei man dann noch dazusagen sollte, dass man

[MethodImpl (MethodImplOptions.NoInlining)]

vor jede Property schreiben muss, in der man OnPropertyChanged benutzt, weil sonst die Wahrscheinlichkeit hoch ist, dass die Property gar nicht im StackTrace auftaucht und entsprechend auch nicht ausgelesen werden kann.

herbivore

6.911 Beiträge seit 2009
vor 14 Jahren

Hallo herbivore,

berechtigter Einwand.

Wird die Property aber so codiert


private string _name;
public string Name
{
	get { return _name; }
	set
	{
		if (_name == value)
			return;

		_name = value;
		OnPropertyChanged();
	}
}

wird kein Inlining durchgeführt (wegen dem return-Statement). Und somit ist der explizite Hinweis mit dem Attribut nicht unbedingt notwendig.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo gfoidl,

wieso sollte wegen des returns kein Inlining möglich sein? Es ist nicht nur möglich, sondern ich habe genau deinen Code genommen und im Release wird da tatsächlich Inlining verwendet. Mich hätte eher gewundert, wenn nicht.

herbivore

6.911 Beiträge seit 2009
vor 14 Jahren

Hallo herbivore,

wieso sollte wegen des returns kein Inlining möglich sein?

Für die Schlussfolgerung gibt es keinen Beweis daher ist sie wohl nicht richtig. Sie kam deshalb zu Stande da die Empirie dies bei mir (Win XP, .net 3.5 SP1, 32 bit) zeigt.

Zum Testen wurde dieser Code verwendet:


using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;

namespace ConsoleApplication1
{
	class Program
	{
		static void Main(string[] args)
		{
			Foo foo = new Foo();
			foo.PropertyChanged += new PropertyChangedEventHandler(foo_PropertyChanged);

			foo.Name = "Hallo";
			Console.ReadKey();
		}
		//---------------------------------------------------------------------
		static void foo_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			Console.WriteLine(e.PropertyName);
		}
	}
	//-------------------------------------------------------------------------
	class Foo : INotifyPropertyChanged
	{
		public event PropertyChangedEventHandler PropertyChanged;
		//---------------------------------------------------------------------
		protected void OnPropertyChanged()
		{
			MethodBase mb = new StackFrame(1).GetMethod();

			Console.WriteLine(mb);

			string propertyName = mb.Name.Substring(4);	// set_ auslassen.
			OnPropertyChanged(propertyName);
		}
		//---------------------------------------------------------------------
		protected void OnPropertyChanged(string name)
		{
			PropertyChangedEventHandler tmp = PropertyChanged;
			if (tmp != null)
				tmp(this, new PropertyChangedEventArgs(name));
		}
		//---------------------------------------------------------------------
		private string _name;
		public string Name
		{
			get { return _name; }
			set
			{
				if (_name == value)
					return;

				_name = value;
				OnPropertyChanged();
			}
		}
	}
}

Wenn ich das in der Release-Konfiguration laufen lasse lasse kommt auf zwei getesteten Rechner immer das selbe Ergebnis raus:

Mit if (...) return gehts -> wird offensichtlich nicht "Ingelint"
Ohne if (...) -> wird ingelint.

Welchen Rechner hast du oder welche Framework-Version?

Aber mit dem MethodImpl-Attribut ist man auf der sicheres Seite und daher sollte dies verwendet werden.

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo gfoidl,

Windows 2000, 32bit, .NET 2.0

herbivore

6.911 Beiträge seit 2009
vor 14 Jahren

Windows 2000

Vernüftig 😉

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

5.742 Beiträge seit 2007
vor 14 Jahren

Hallo zusammen,

im Vergleich zum Auslesen aus dem StackTrace ist die Methode mit der Expression aber IMHO um einiges eleganter, performanter und sicherer.
Darauf, dass unter gewissen Umständen eventuell das Attribut redundant sein könnte, sollte man sich überhaupt nicht verlassen - ein neuer JIT-Compiler oder ngen können das alles zunichte machen 😉

Ich persönlich wünsche mir aber immer noch etwas wie:


public class Foo
{
   public string Bar
   {
       get;
       set;
   }
}
//...
string propertyName = typeof(Foo.Bar).Name;

wobei das typeof(Foo.Bar) ein PropertyInfo liefert.

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo winSharp93,

bei der Performance gebe ich dir Recht, bei der Eleganz gibt es zwei Seiten und bei der Sicherheit kann ich deinen Standpunkt nicht nachvollziehen.

Der Performance über den StackTrace ist relativ schlecht. Daraus habe ich nie einen Hehl gemacht. Ob sie bei Properties, die in der Regel auf der Oberfläche angezeigt werden (INotifyPropertyChanged steht ja in enger Beziehung zum DataBinding) zu schlecht ist, glaube ich aber nicht.

Wenn man den Namen gar nicht angeben muss, ist das auf jeden Fall extrem elegant. Besser geht es nicht. Dass der Name dann über den StackTrace ermittelt wird und dabei sogar noch Substring verwendet werden muss, ist natürlich sehr unschön, aber eben Mittel zum Zweck.

Die mögliche Unsicherheit bezüglich neuer, inkompatibler Compiler-Versionen schätze ich als sehr gering ein. Die Unsicherheit, das der Programmierer durch Copy&Paste eine falschen Namen angibt, ist dagegen vollkommen real und wird immer wieder vorkommen. Wenn du diese beiden Aspekte gegeneinander abwägst, ist mir schleierhaft, wie du zu einen anderen Ergebnis kommen kannst.

herbivore

5.742 Beiträge seit 2007
vor 14 Jahren

bei der Sicherheit kann ich deinen Standpunkt nicht nachvollziehen.

"Sicherheit" ist vermutlich das falsche Wort.
Was ich sagen wollte, war die Zuverlässigkeit bzw. die "Sicherheit", dass es auch immer funktioniert.

Wenn man den Namen gar nicht angeben muss, ist das auf jeden Fall extrem elegant. Besser geht es nicht.

Da stimme ich dir zu.

Dass der Name dann über den StackTrace ermittelt wird und dabei sogar noch Substring verwendet werden muss, ist natürlich sehr unschön, aber eben Mittel zum Zweck.

Wobei ich mir die Frage stelle, ob es das wirklich wert ist.
Was passiert, wenn man OnPropertyChanged nicht aus einer Property heraus aufruft?
Zum Beispiel aus einer anderen Methode oder aus einer Property, welche die Änderung einer anderen zur Folge hat (weil sie auf dem geänderten Wert basiert).

Ein weiteres Szenario: Durch etwas Refaktoring hat man plötzlich eine Methode OnNameChanged, in der man PropertyChanged aufruft - und dann funktioniert der Code auf einmal nicht mehr...

Eventuell setzt man noch irgendwelche Rewriter ein (Aspektorientierung oder was auch immer), die den Stacktrace ändern.

Die mögliche Unsicherheit bezüglich neuer, inkompatibler Compiler-Versionen schätze ich als sehr gering ein

Das bezog sich eher auf das Weglassen des expliziten NoInlining.

Wenn du diese beiden Aspekte gegeneinander abwägst, ist mir schleierhaft, wie du zu einen anderen Ergebnis kommen kannst.

Ein paar Gründe habe ich ja schon genannt, aber um es zu verallgemeinern:
Eine Methode sollte sich nicht in diesem Grade (eigentlich überhaupt nicht) darauf verlassen, wie der Callstack aussieht bzw. wer sie aufgerufen hat.
Außerdem erwartet man dieses Verhalten auch nicht von einer derartigen Methode; sie "bricht" in ihrem Verhalten sonst eher Konvenktionen.
Eine Methode, die statt eines Parameters vom Aufrufer verlangt, an einer ganz bestimmten Stelle aufgerufen zu werden, halte ich für sehr schlecht.

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo winSharp93,

Was passiert, wenn man OnPropertyChanged nicht aus einer Property heraus aufruft?

ich würde sowieso nicht OnPropertyChanged direkt aufrufen, sondern eine extra Methode schreiben, die den Namen der Property ermittelt, die EventArgs erzeugt, den Namen reinschreibt und damit dann OnPropertyChanged aufruft. Und diese Methode muss dann - per Definition - immer direkt aus einer Property aufgerufen werden. In allen von dir genannten Fällen - dir ich alle für nicht besonderes wahrscheinlich halte -, kann man dann diese Methode eben nicht verwenden, sondern muss OnPropertyChanged selbst mit den passenden EventArgs mit dem passenden Namen aufrufen. Insofern hat man dann weiterhin die volle Kontrolle und die volle Flexibilität, gleichzeitig aber eine große Erleichterung für den Standard-Fall.

herbivore

5.742 Beiträge seit 2007
vor 14 Jahren

sondern eine extra Methode schreiben, die den Namen der Property ermittelt, die EventArgs erzeugt, den Namen reinschreibt und damit dann OnPropertyChanged aufruft.

Also etwas wie:


public int Foo
{
     //...
    set
    {
        this.OnPropertyChanged(Helper.GetCallingPropertyName());
    }
}

Ok - das wäre tatsächlich sauber.