Generische Methode um gleichnamige Properties einer Klasse zu einem Objekt einer anderen Klasse zu kopieren – C#

Bei meiner Arbeit kommt es häufiger vor, dass ich umfangreiche Klassen (welche die Daten einer Datenbank repräsentieren) verwende die mal schnell über 50 oder 100 Public Properties (Datenbankfelder) enthalten.

imageNun kommt immer wieder vor, dass ich zum Beispiel zum Archivieren solcher Daten, damit meine ich die Daten aus einer Tabelle in eine andere zu verschieben, nicht auf SQL Skripte auf dem Server zugreifen kann oder möchte, da dieses verschieben noch mit zusätzlicher Businesslogik hinterlegt werden muss.

Was bleibt ist dann so etwas in dieser Art:

var auftrag = new Auftrag
    { Position = 1, Artikel = "Mars", Beschreibung = "Schoko Riegel", Preis = 1.50 };

var auftragsArchiv = new AuftragArchiv();
auftragsArchiv.Artikel = auftrag.Artikel;
auftragsArchiv.Beschreibung = auftrag.Beschreibung;
auftragsArchiv.Preis = auftrag.Preis;

if (auftragsArchiv.Save())
{
    auftrag.Delete();
}

Diese Vorgehensweise kann bei umfangreichen Klassen sehr arbeitsintensiv sein.

Nun gibt es verschiedene Ansätze um sich diesem Problem anzunehmen. Nachfolgend möchte ich einige Lösungsansätze unter Verwendung von Reflection aufzeigen.

Ich nehme an, jeder kennt die Methode ToString().

Die Idee ist nun eine generische To…. Methode zu erstellen, welche die Aufgaben wie im obigen Beispiel dargestellt, universell erledigt.

Das Ziel soll sein, dass die Public Property Werte eines Objektes, durch Aufruf einer Methode, alle Werte die den gleichen Property Namen  haben in ein anderes Objekt einer anderen Klasse kopiert werden.

Hier nun die Variante 1 (Methode einer Klasse):

public T ToTobject<T>(T target)
{
    const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public;
    var sourceProperties = this.GetType().GetProperties(Flags);

    foreach (PropertyInfo pi in sourceProperties)
        {
            if (pi.CanWrite)
            {
				var propInfoObj = target.GetType().GetProperty(pi.Name);
				if (propInfoObj != null)
				{
					var propValue = pi.GetValue(this, null);
					propInfoObj.SetValue(target, propValue, null);
				}                             
            }
        }

    return target;
}

Der Aufruf um diese Variante zu verwenden sieht dann so aus:

var auftrag = new Auftrag
	{ Position = 1, Artikel = "Mars", Beschreibung = "Schoko Riegel", Preis = 1.50 };

var auftragsArchiv = new AuftragArchiv();

auftragsArchiv = auftrag.ToTobject(auftragsArchiv);

if (auftragsArchiv.Save())
{
	auftrag.Delete();
}

Sieht doch schon ganz gut aus.

Hier noch eine Variante, die noch etwas mehr generisch arbeitet und nur noch mitgeteilt bekommt, welchen Type ich als Rückgabe der Methode erwarte, die Instanz des Objekts der neuen Klasse wird in der Methode selbst erzeugt.

public T ToTobject<T>()
{
	var constructorInfo = typeof(T).GetConstructor(new Type[] { });
	if (constructorInfo != null)
	{
		var target = (T)constructorInfo.Invoke(new object[] { });

		const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public;
		var sourceProperties = this.GetType().GetProperties(Flags);

		foreach (PropertyInfo pi in sourceProperties)
		{
			if (pi.CanWrite)
			{
				if (!pi.IsDefined(typeof(XmlIgnoreAttribute), false))
				{
					var propInfoObj = target.GetType().GetProperty(pi.Name);
					if (propInfoObj != null)
					{
						var propValue = pi.GetValue(this, null);
						propInfoObj.SetValue(target, propValue, null);
					}
				}
			}
		}

		return target;
	}

	return default(T);
}

Diese Variante wird dann so verwendet:

var auftrag = new Auftrag
	{ Position = 1, Artikel = "Mars", Beschreibung = "Schoko Riegel", Preis = 1.50 };

var auftragsArchiv = auftrag.ToTobject<AuftragArchiv>();

if (auftragsArchiv.Save())
{
	auftrag.Delete();
}

Diese Variante gefällt mir schon etwas besser

Aber das ist noch nicht das Ende der Methoden Evolution!

Es geht noch generischer (und statisch für alle Klassen verwendbar) und diese Variante folgt nun.

public static T ToTobject<T>(object source)
{
	var constructorInfo = typeof(T).GetConstructor(new Type[] { });
	if (constructorInfo != null)
	{
		var target = (T)constructorInfo.Invoke(new object[] { });

		const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public;
		var sourceProperties = source.GetType().GetProperties(Flags);

		foreach (PropertyInfo pi in sourceProperties)
		{
			if (pi.CanWrite)
			{
				var propInfoObj = target.GetType().GetProperty(pi.Name);
				if (propInfoObj != null)
				{
					var propValue = pi.GetValue(source, null);
					propInfoObj.SetValue(target, propValue, null);
				}
			}
		}

		return target;
	}

	return default(T);
}

Diese nun statische Variante kann auf jedes beliebige Objekt jeder beliebigen Klasse angewendet und für die Rückgabe jeder beliebigen Klasse verwendet werden. Natürlich nur solange diese Klassen auch gleiche Properties enthalten.

Und so wird diese statische Methode verwendet:

var auftrag = new Auftrag
	{ Position = 1, Artikel = "Mars", Beschreibung = "Schoko Riegel", Preis = 1.50 };

var auftragsArchiv = Auftrag.ToTobject<AuftragArchiv>(auftrag);

if (auftragsArchiv.Save())
{
	auftrag.Delete();
}

Mir hat es Spaß gemacht diese Methodik zu entwickeln, evtl. hilft es auch mal jemand anderem beim Lösen “seines” Problem.

Anregungen und Kritik wie immer gerne als Kommentar zum Beitrag.