Feedback

C# - Call-By-Reference Ersatz durch Field- und Property-Mapping

Veröffentlicht von am 11/29/2012
(2 Bewertungen)
Im Internet existieren zahlreiche Diskussionen warum diese Zeilen Code nicht so funktionieren, wie man es instinktiv erwartet:
public class CA {
public String t1;
}

void MethodExampleA (ref Object obj) {
... // Bearbeitung des Objektes obj
}

void MethodExampleB (Object obj) {
... // Bearbeitung des Objektes obj
}

CA ca = new CA();
ca.t1 = "1";
MethodExampleA (ref ca); // nicht möglich
MethodExampleB (ca); // vergebens


// Edit: 2013-01-29 -- Danke an Scavanger, vergass ich zu erwähnen
<b>Da der Typ zur Compile-Zeit nicht bekannt</b> ist funktioniert ein (obj as CA).t1 = "Hallo"; leider nicht.

Hintergrund: Die aufgerufene Funktion liegt in einer DLL die von mehreren Programmen mit verschiedenen Datenstrukturen genutzt wird.

Eine Alternative ist natürlich eine generische Funktion ala MethodExampleC T (T obj). Ein obj.t1 macht trotzdem keinen Sinn, da zur Compile-Zeit <b>der DLL</b> ja nicht bekannt.
// Edit

Gemein ist: innerhalb der Funktion 'MethodExampleB' funktioniert alles. Nur nach dem Aufruf ist alles beim Alten geblieben. Wieso?


Was man (und ich anfangs auch) gerne vergisst: 'Object' ist kein Container für andere Datentypen, sondern selbst ein Datentyp. Daher wird beim Aufruf der Funktion der übergebene Type 'CA' quasi nach 'Object' konvertiert. Deshalb funktioniert der Code überhaupt ohne Fehlermeldung.
Diese Konvertierung ist - natürlich - temporär und verfällt bei Rückkehr aus der Funktion. Und damit auch die Änderungen am 'Object obj' - und eben nicht an 'CA ca'.

Hin und wieder wäre es aber schön, wenn es doch ginge.

Nicht ganz das selbe, aber in vielen Fällen ausreichend ist ein 'Value-Mapping'. Also das 'mappen' der tempörären Änderungen an 'Object obj' auf Werte- oder Referenz-Typ-Ebene innerhalb von 'CA ca'. Für 'public' Fields und 'Propertys erreicht man das so.
void MapPublicValues (Object Source, Object Target) {
	Type type;
	Object sub_source;
	
	if (Source == null) return; // macht wohl keine Sinn
	if (Target == null) return; // das auch nicht
	
	type = Source.GetType ();
	foreach (PropertyInfo pi in type.GetProperties ()) {
		sub_source = pi.GetValue (Source, null);
		pi.SetValue (Target, sub_source, null);
	}
	foreach (FieldInfo fi in type.GetFields ()) {
		sub_source = fi.GetValue (Source);
		fi.SetValue (Target, sub_source);
	}
}

// Interessant: es wird keine Referenz dazu benötigt.
void MethodExample (Object obj) {
	Object tmp;
	
	tmp = (Object) Activator.CreateInstance(obj.GetType());
	.... // tmp bearbeiten
	// obj = tmp;  : sinnlos, wird Rückkehr verworfen
	MapPublicValues (tmp, obj); // Änderungen nach obj 'mappen'
}

3 Kommentare zum Snippet

Scavanger schrieb am 12/10/2012:
Bitte was?!?


CA ca = new CA();
ca.t1 = "1";
MethodExample (ref ca); // vergebens


Dieser Code ist nicht nur vergebens, sondern nicht mal kompilierbar: "1-Argument: Kann nicht von "ref TestProjekt.CA" in "ref object" konvertiert werden:"

Ein Objekt ist in C# immer eine Referenz (auf das Daten im Heap). "ref Object" wäre eine Referenz auf eine Referenz, das gibt es in C# nicht, in C sind Zeiger (aka Referenzen) auf Zeiger möglich.


void MethodExample(Object obj)
{
(obj as CA).t1 = "Hallo";
}
// ..
CA ca = new CA();
ca.t1 = "1";
MethodExample(ca);


Funktioniert wie erwartet.
Also warum der umständliche und langsame Weg über Reflection?
Scavanger schrieb am 12/10/2012:
Sorry, Berichtigung:

Referenzen auf von object abgeleitet Objekte funktionieren natürlich, sind aber in Methodensignaturen unnötig, da eine Variable von einem Objekt eine Refernenz auf die Daten im Heap sind. "ref CA obj" wäre kompilierbar, aber unnötig.

"ref" benötigt immer den konkreten Typ!
diub schrieb am 1/29/2013:
@Scavanger: ein 'Sorry' ist nicht nötig ;-)

Das Problem, oder vieleicht besser mein Problem, lässt sich so nicht lösen.
Der ist Typ zur Compile-Zeit *nicht* bekannt. Das müsste er dafür aber sein.

(obj as CA).t1 = "Hallo"; // das ist *nicht möglich* Typ unbekannt!!!!


So geht es auch nicht: (obj as <b>obj.GetType ()</b>).t1 = "Hallo";
 

Logge dich ein, um hier zu kommentieren!