Laden...

Ohne Reflektion, this Parameter übergeben und (z.B. aus Variablenname) eine ID generieren

Erstellt von Teak vor 6 Jahren Letzter Beitrag vor 6 Jahren 1.847 Views
T
Teak Themenstarter:in
2 Beiträge seit 2017
vor 6 Jahren
Ohne Reflektion, this Parameter übergeben und (z.B. aus Variablenname) eine ID generieren

Für eine sehr Zeitkritische Funktionalität möchte ich mir etwas basteln damit das Programmieren/verwenden in neuen Objekten, dann weniger Tipparbeit macht.
Ich will also eine generische Funktionalität, welche jedoch keinerlei Reflexion verwendet.

Es geht darum, dass im Programmcode bei bedarf einfach und schnell Synchronisierte Objekte hinzu gefügt werden können sollen.

Was mir nun nicht gefällt an meinem Code ist, das man beim initialisieren eines neuen SynchronizedObject, diesem neuen Object per Hand eine ID zuweisen muss (im Beispiel die 4) und 'this' übergeben muss. 'this' ist der Parameter für den Besitzer des SynchronizedObject. Es ist also IMMER this.

Hat jemand eine Idee, wie ich 'this' automatisiert einfügen kann, so das nicht jedesmal dies per Hand gemacht werden muss?
.....irgendwie vermisse ich gerade doch ein wenig von c/c++ das #define

Oder jemand eine Idee, wie die Sache mit der ID, schöner umgesetzt werden kann?
Problem an der ID ist auch, das wenn nun ein Objekt mit der ID 4 synchronisiert wird, dann soll zukünftig diese gleiche Variable immer die 4 haben! Auch wenn im Quellcode z.B. mal das Object mit der ID 3 raus fällt, soll ein Object mit der ID 4, nicht auf 3 'nachrutschen'. Denn die ID wird an anderer Stelle auch verwendet um das Object (seine Werte etc.) persistent zu speichern.
Am liebsten wäre mir also eigentlich, wenn anhand des Namens der Variable, diese ID automatisch generiert würde. So würde ein gleicher Variablenname immer die gleiche ID ergeben. Doch leider ohne Reflektion... 😕

Wenn man also eine neue Klasse schreibt, dann kann auf folgende Art, ein neues Synchronisiertes Objekt, dieser Klasse hinzu gefügt werden:


// Verwendung von  SynchronizedObject in einer neuen Klasse
public class IrgendeineNeueKlasse : SyncBehaviour{
	public SynchronizedObject  neueVar;

	public void Init() {
		neueVar = new SynchronizedObject(4, this, 7);  // ID 4, Owner 'this', Initialwert 7
	}

	public void DoAnyWork() {
		int bla = neueVar + 3;		// 7 + 3 => bla = 10;  => einfaches abfragen des aktuellen Wertes
		neueVar |= 15;			// Wert von neueVar wird von 7 auf 15 geändert. Wird im Hintergrund automatisch synchronisiert.
			// Verwendung des | Operator, da der = Operator das Objekt selber 'überschreiben' würde, anstelle ihm 'nur' einen Wert zu übergeben.
	}
}



// Verwaltungscode der im Hintergrund alles erledigt
public class SynchronizedObject {
	private int _value;
	private bool isDirty = false;

	public SynchronizedObject(byte id, SyncBehaviour owner, int value) {
		_value = value;
		owner.RegisterNewSyncObject(id, this)	// <= owner ! genau deswegen brauche ich den 'this' Parameter :-/  in der Zeile mit:
										// 'neueVar = new SynchronizedObject(4, this, 7);'
	}

	public static SynchronizedObject operator | (SynchronizedObject valueThis, int valueOther) {
		if (valueThis._value == valueOther)
			return valueThis;
		isDirty = true;
		valueThis._value = valueOther;
		return valueThis;
	}

	public static implicit operator int(SynchronizedObject valueThis) {
		return valueThis._value;
	}

	public void Sync(byte[] data, bool writeToData) {
		if (writeToData) {
			 isDirty = false;
			// Schreibe die bytes von _value nach data
		} else {
			// Lese von data und überschreibe _value
		}
	}
}


public class SyncBehaviour {
	// <Liste mit SynchronizedObject's>

	public void RegisterNewSyncObject(byte id, SynchronizedObject syncObj) {
		// syncObj in <Liste mit  SynchronizedObject's> einfügen.
		// Identifiziert wird das syncObj mittels seiner ID
	}

	protected void SyncAllSynchronizedObjects() {
		//  <Liste mit  SynchronizedObject's> durchgehen und bei allen Objekten in der Liste die Sync() funktion aufrufen.
	}
}
3.003 Beiträge seit 2006
vor 6 Jahren

Wait, what? Du überlädst den binären oder-Operator so, dass er sich nicht mehr wie ein binärer oder-Operator verhält?

Na, was soll dabei schon schiefgehen.

Zur eigentlichen Frage: was du da machst, erreicht man sauber und ohne Aufwand mit Events.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

1.040 Beiträge seit 2007
vor 6 Jahren

Teak, kannst du mal den Sinn dahinter erklären? Oder gerne auch LaTino. 😄

2.080 Beiträge seit 2012
vor 6 Jahren

Was das Überladenen des binären Operator angeht:
Lass es bleiben

Was Du an Zeit sparst, weil Du ein paar Zeichen weniger schreiben musst, verlierst Du später durch die Komplexität, die Du dir hier baust.
Außerdem ist der Gewinn dank VisualStudio, was dir Properties und Co. automatisch einfügen kann, recht gerig. Es braucht nur genug Übung, um die Hilfen von VS in den Arbeitsalltag zu integrieren.

Was die ID angeht:
Ist Guid eine Option? Das kannst Du im ctor setzen und Guid ist dafür da, eindeutig zu sein.
Wenn das keine Option ist und Du dennoch eine eindeutige ID haben willst, dann bau dir doch irgendwo eine Klasse (Singleton und threadsafe), die dir eine neue ID gibt, die gleichzeitig intern vor hält und bei einem erneuten Aufruf eine neue ID zurück gibt.
Das kannst Du dann auch im ctor aufrufen und musst die ID nicht mehr als Parameter mit geben.

Beispiel:

public class IdSource
{
    public static IdSource { get; } = new IdSource();

    private readonly ICollection<int> _ids;

    private IdSource()
    {
        _ids = new List<int>();
    }

    public int GetId()
    {
        var newId = _ids.Max() + 1;
        _ids.Add(newId);
        return newId;
    }
}

Übrigens: byte ist nicht zwangsläufig besser als int, weil es kleiner ist. Wenn Du die Wahl hast, dann nimm lieber int.
Mehr dazu: https://stackoverflow.com/questions/2346394/should-i-use-byte-or-int
Das aber nur als Hinweis am Rand

Und das automatische Übergeben von this:
Das geht nicht und lohnt mMn. nicht.
Der Vorteil ist minimal und der Aufwand - wenn man das irgendwie erreichen könnte - viel höher.

Irgendwie klingen diese Feature-Wünsche nach Mikro-Optimierungen, die in den meisten Fällen nicht lohnen.
Der Initial-Aufwand (allein der Thread hier schon) und auch nachfolgende Aufwände durch die höhere Komplexität gleichen den geringen Vorteil vermutlich doppelt und dreifach aus.

Viel wichtiger ist ein kluges Konzept beim Aufbau der Anwendung, dass ihr euch keine Steine in den Weg stellt, die euch in einem späteren Entwicklungsstand zum Umdenken/Umbauen zwingen.

Was Du aber machen könntest:
Erstell dir ein Snippet für sich häufig wiederholenden Code.
Solche Snippets kannst Du bei der Arbeit nutzen und sparst dir damit tatsächlich einiges an Zeit 😉

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

709 Beiträge seit 2008
vor 6 Jahren

Für die Erstellung könnte sich eine protected Methode in der SyncBehaviour-Klasse (z.B. SynchronizedObject CreateSynchronizedObject(int value) anbieten. Darin könnte man dann auch über eine der bereits erwähnten Arten die ID zuweisen.
Innerhalb dieser Methode könnte man ja mit this arbeiten und hat dann in den abgeleiteten Methoden nicht so viel Schreibarbeit.

Elegant finde ich diesen Weg aber auch nicht wirklich...

16.842 Beiträge seit 2008
vor 6 Jahren

Man sollte evtl. auch einfach nicht versuchen die Kultur von C/C++ Eins-zu-Eins in C# umsetzen zu wollen.
Das geht in der Regel in die Hose.

T
Teak Themenstarter:in
2 Beiträge seit 2017
vor 6 Jahren

@pinki: Dir besonderen dank für deine Antwort und das du wirklich auf mein Anliegen eingegangen bist 🙂 Deine Idee ist gut. An soetwas hatte ich garnicht gedacht! Ich werde das überdenken.

@LaTino: Ok, diesen Operator dafür zu missbrauchen ist wirklich nicht sooo super. Doch, wann kommt man jemals auf die Idee, IRGENDEINEN der bit-operatoren, leichtfertig zu benutzen oder gar 'versehentlich' oder unbedacht?
Also mir ist es noch nie passiert und in rund 20 Jahren beruflichem programmieren, ist mir auch noch kein Kollege begegnet, der versehentlich oder sonst wie einen Bitoperator verwendet hat ohne das es ganz bewusst geschah. Mal ehrlich, einen Bitoperator tippt man doch nun wirklich nur sehr bewusst ein! Entwerder weil man genau weiss was für ein Integer-Datentyp es ist und man gezielt und bewusst die Bits verändern möchte, oder eben weil man weiss das es ein nicht-integer-Datentyp ist der in besonderer Art einen Bitoperator nutzt. Stichwort c++ und serialisieren 😉
Von daher halte ich solche Bedenken eher für .. nunja, Philosophischer Natur.. oder.. wie eine Abscheu vor etwas was eigentlich Geschmackssache ist. z.B. wie 'Igitt! Octopus! Das esse ich nicht!'
Das es 'gefährlich' sein soll, kann ich von daher bei dem obigen Code, nicht zustimmen.
Das es Geschmackssache ist und man so etwas furchtbar nervig und unschön finden kann, ja das mag sein. Von dieser Seite her, bin ich auch offen für Vorschläge wie es schöner zu benutzen wäre.
Umgekehrt, finde ich es nervig dauernd eine xxx.Set(#); Funktion aufrufen zu müssen, für eine Funktionalität die 'x = x + 2;' gleichkommen soll.
Und ja, bei uns im Team ist dies derzeit auch eine Diskusion. Allerdings dreht es sich weniger darum ob sondern viel mehr um welchen Operator. z.B.: myObj <≤ 7;

Und, @LaTino, kannst du mich bitte aufklären, wie eine Lösung mit Events aussehen kann? Ich stehe auf dem Schlauch. So sehr ich auch nachdenke, kommt mir keine Idee wie bei diesem obigen Problem, mir Events helfen könnten?
Ja, wir nutzen bei dieser Klasse auch Events (Action) um optional zu ermöglichen, das eine Funktion aufgerufen wird wenn der Wert des Objekts sich geändert hat z.B. OnChangeBlaBla()
Doch, wie könnten Events hier noch helfen? Oder meinst du etwas anderes?

@Palladin007: Guid etc. ist keine Lösung, da bei jedem neu compilieren des Programms, und auch nach Codeänderungen etc. jedes mal dem Objekt die gleiche ID zugewiesen werden müsste. Stichwort: die ID wird benutzt um im Hintergrund die Daten auch in Datei zu speichern/laden.
Desweiteren danke ich dir für deine Denkanstöße!

@All: Danke! Anscheinend störe nur ich mich so sehr an der Art wie die Objekte erstellt werden müssen? Stadtessen ist die Operator-Überladung das, was der Mehrheit aufstößt?

3.170 Beiträge seit 2006
vor 6 Jahren

Hallo,

Stadtessen ist die Operator-Überladung das, was der Mehrheit aufstößt?

Ja - mir auch.
Und zwar aus einem einfachen Grund: Das Verhalten ist völlig missverständlich, und zwar vor allem, weil Du auch noch eine implicit-Konvertierung nach int hast.

Folgender Code:

SynchronizedObject mySync = new SynchronizedObject(...);
int myInt = mySync;

if(mySync == myInt) // true

// -----

mySync |= 8;
myInt |= 8;

if(mySync == myInt) // false

Das halte schon für verwirrend und vor allem inkonsistent und würde davon unbedingt absehen.
Früher oder später verwechselt jemand da etwas und dann fliegt es Euch um die Ohren.

Daher eigent sich IMO auch kein anderer Operator, zumindest keiner, der auch auf int anwendbar wäre.

Die Semantik ist hier ganz klar die einer Set-Methode und sollte dann auch so implementiert werden.
Was ist daran eigentlich so schlimm? Dank Intellisense musst Du noch nicht mal mehr tippen, auch wenn die Zeile 2 Zeichen(!) länger wird als mit dem Operator.

Gruß, MarsStein

Edit: Diese Antwort bezieht sich nur auf die Operator-Überladung an sich. Nicht auf das gesamte Konzept. Da würde ich auch zusehen, ob man das nicht ganz anders umsetzt.

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

16.842 Beiträge seit 2008
vor 6 Jahren

Kann evtl. irgendwie der Anforderungsgrund beschrieben werden, warum das so und so sein muss?
Ich hab immer noch das Gefühl, dass hier versucht wird C/C++ Konzepte in C# unterzumogeln statt entsprechende Referenz-Konzepte zu nutzen.

T
2.224 Beiträge seit 2008
vor 6 Jahren

@Teak
Ich muss hier Abt zustimmen.
Ich habe auch das Gefühl, dass du hier zu viel aus C++ in C# reinbaust.
C# hat mit den Setter und Getter einen schönen Syntax, der vom Compiler in Set/Get Methoden compiliert wird.
In C# schreibt keiner mehr Code wie Set...(Typ Variable)/Get...()
Das ist C++ und braucht man nicht, da C# mit den Properties eine elegante Lösung hat.
Selbst Java wird in den nächsten Versionen vermutlich nachziehen.
C++ dürfte erst in ein paar Jahren dort sein.

Wenn ihr mit C# ernsthaft arbeitet, dann lernt die Sparche und Paradigmen.
Ist am Anfang auch erst einmal viel neue, aber der Aufwand lohnt sich ungemein.
Ich bereue es bis Heute nicht, dass ich mit C++ angefangen und später beruflich dann zu C# umgestiegen bin.
Dies ist bis heute um längen eleganter zu programmieren als reine C++

Ansonsten schließe ich mich auch MarsStein an.
Ich rund 10 Jahren mit C# musste ich noch nie einen Operator überladen, einfach weil es nie nötig war.
Auch in eurem Fall sehe ich keinen Mehrwert.
Es führt einfach zu Unklarheiten und einem fehlerhaften Verhalten, was man von dem Operator nicht erwarten würde.

Ihr solltest eurer Konzept nochmals überdenken, dann kann der Code wahrscheinlich vereinfacht und aufgeräumt werden.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

3.003 Beiträge seit 2006
vor 6 Jahren

@LaTino:. Mal ehrlich, einen Bitoperator tippt man doch nun wirklich nur sehr bewusst ein!

Stimmt, damit hast du den Grund für meine Kritik präzise in einen Satz gefasst. Es hat immer - IMMER - das Prinzip der geringsten Überraschung zu gelten. Möglicherweise benutzt jemand deine Klasse, um Werte einer auf int basierenden flags-Enumeration zu speichern, und will dem aktuellen Wert ein Bit hinzufügen. Na, der wird sich umschauen.

Der Punkt ist, und ich formuliere es mal ganz hart: was du da machst, ist nicht weiter als möglichst originellen Code zu schreiben und sich auf die eigene Cleverness einen runter....naja. Nur: weder ist es clever, noch ist es kollegial, noch professionell. Wenn du das Bedürfnis nach so was hast: dafür gibt's Sprachen wie Brainfuck.

Der Rest deines Codes ist eine sehr krude Neuerfindung des Observer-Patterns. Wird in c# wie gesagt mit Events geregelt, in deinem Fall würde die Methode syncall einfach das Event feuern, und die Abonnenten würden sich dann eben synchronisieren.

Aber das wäre dir aufgefallen, wenn du nicht auf Gedeih und Verderb Elemente aus C in c# abbilden wollen würdest.

LaTino
EDIT: deine Operatorüberladung funktioniert übrigens nichtmal, du greifst im statischen Kontext auf eine Membervariable zu.

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

3.003 Beiträge seit 2006
vor 6 Jahren

Damit mir keiner unkonstruktive Kritik vorwirft.


public delegate void SynchronizingEventHandler(byte[] data, bool writeToData);

public interface ISyncBehaviour
{
    event SynchronizingEventHandler Synchronizing;
    void RegisterNewSyncObject(SynchronizedObject syncObj);
    void SyncRead();
    void SyncWrite();
}

//"Observable"
public class SynchronizedObject
{
    private bool _isDirty;
    private int _value;

    public int Value
    {
        get => _value;
        set
        {
            if (EqualityComparer<int>.Default.Equals(_value, value)) return;
            _value = value;
            _isDirty = true;
        }
    }

    public SynchronizedObject(int value)
    {
        _value = value;
    }

    public void Sync(byte[] data, bool writeToData)
    {
        if (!writeToData) Value = BitConverter.ToInt32(data, 0);
        else
        {
            if (!_isDirty) return;
            _isDirty = false;
            var valueBytes = BitConverter.GetBytes(Value);
            Array.Copy(valueBytes, data, sizeof(int));
        }
    }
}

//"Observer"
public class ExampleClass : ISyncBehaviour
{
    public byte[] Data { get; } = new byte[sizeof(int)];

    public void Init(int value)
    {
        var bytes = BitConverter.GetBytes(value);
        Array.Copy(bytes, Data, sizeof(int));
    }

    #region Kann auch in eine abstrakte Basis verlagert werden
    public void RegisterNewSyncObject(SynchronizedObject syncObj)
    {
        Synchronizing += syncObj.Sync;
    }

    public void SyncRead() => OnSynchronizing(Data, true);
    public void SyncWrite() => OnSynchronizing(Data, false);

    public event SynchronizingEventHandler Synchronizing;
    protected void OnSynchronizing(byte[] data, bool writeToData) => Synchronizing?.Invoke(data, writeToData);
    #endregion
}


Natürlich wird bei mehreren synchronisierten Objekten data immer wieder überschrieben, aber das ist ja quasi eine Anforderung von dir (soll heissen: über das Thema hast du kein Wort verloren, und es ist auch in deinem Code so.)

Anwendung:


var synchronizedObjects = new List<SynchronizedObject> {new SynchronizedObject(10), new SynchronizedObject(20)};

var syncingContext = new ExampleClass();
syncingContext.Init(-100);
synchronizedObjects.ForEach(syncingContext.RegisterNewSyncObject);

syncingContext.SyncWrite(); //alle SynchronizedObjects haben jetzt denselben Wert
synchronizedObjects.ForEach(p => Console.WriteLine(p.Value));

synchronizedObjects.First().Value = 9999; //beide "dirty" markieren, damit sich beide melden
synchronizedObjects.Last().Value = 99;

syncingContext.SyncRead(); //der Wert aller SynchronizedObjects wird nach Data gelesen. Das letzte gewinnt.
Console.WriteLine(BitConverter.ToInt32(syncingContext.Data, 0));

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

B
66 Beiträge seit 2013
vor 6 Jahren

@Teak:
Selbst unter C/C++ Entwicklern hast Du einen Unterhaltungswert ... 😁
Die Idiome einer Programmiersprache sind immer entscheidend, wenn man sich in die Kurve legen will.
Idiom (Softwaretechnik)
Der einzige Grund für so einen Firlefanz wäre die zeitkritische Betrachtung:

Auch c# kennt Makros:
https://docs.microsoft.com/de-de/dotnet/csharp/language-reference/preprocessor-directives/preprocessor-define

Hier hast Du ein paar Idione aus C# die dir vielleicht helfen. Welche die schnellsten sind musst Du selbst testen:

  1. Beim Bit-weisen Festlegen gibt es das sog. FlagAttribute
    https://msdn.microsoft.com/en-us/library/system.flagsattribute(v=vs.110).aspx
    (Flak gefällt mir besser. 😄)
               
          //  To add you can just do:
            MyFlags flags = MyFlags.Pepsi;
            flags |= MyFlags.Coke;

            //To remove you can do:
            MyFlags flags = MyFlags.Pepsi | MyFlags.Coke;
            flags &= ~MyFlags.Coke;
            
          //  Do not use XOR (^), it adds the flag if it does not exist.
            flags ^= MyFlags.Coke; // Do not use!!!


  1. Du kannst die ID Definition direkt mit base an die basiklasse (aka c++ Superklasse) übergeben.
    https://docs.microsoft.com/de-de/dotnet/csharp/language-reference/keywords/base
    Boing!!

  2. In C# hast ebenfalls ref und noch interessanter out zur Verfügung.
    https://docs.microsoft.com/de-de/dotnet/csharp/language-reference/keywords/ref
    https://docs.microsoft.com/de-de/dotnet/csharp/language-reference/keywords/out-parameter-modifier
    Ist die schnellste Lösung innerhalb c#!
    In C/C++ nutzen wir die Referenz "&" und die Pointer "*" und "this->"

  3. Auch in C# haben virtuelle und abstrakte Klasse zur Verfügung, wo man bei einen nul object einfach mal override kann:
    https://docs.microsoft.com/de-de/dotnet/csharp/language-reference/keywords/override

  4. Auch generische Lösung wie class ImplSync<T,x,y>: baseSync<T> sind in C# möglich.
    https://docs.microsoft.com/de-de/dotnet/csharp/programming-guide/generics/generic-classes

  5. OPERATOR OVERLOADS SIND EIN KLARES c++ IDIOM!
    Da c# seinen eigenen Garbage collector hat und ein dtor überflüssig ist und mit null ref einfach gelöscht werden kann, habe ich so gut wie nie eine praktische Anwendung in C# gefunden. Und ich habe ca. 1 M LOC C++ auf den Kerbholz...

  6. Bei Autogenerierten Instanzen ist die GUID ganz hilfreich.
    https://msdn.microsoft.com/de-de/library/system.guid(v=vs.110).aspx