Tag Archive for 'type conversion'

Nachtrag: TypeConversions reloaded

Ich hatte ja hier schon etwas zu eigenen impliziten und expliziten Konvertierungen geschrieben. Dazu noch ein Nachtrag zur Konvertierungen zwischen zwei eigenen Datentypen. Wir haben wieder unseren Datentypen MyDataType:

class MyDataType
{
	public string Wert { get; set; }
	public static implicit operator string(MyDataType toConvert)
	{ return toConvert.Wert; }

	public static implicit operator MyOtherDataType(MyDataType toConvert)
	{
		int Converted;
		if (Int32.TryParse(toConvert.Wert, out Converted))
			return new MyOtherDataType() { Wert = Converted };
		else
			return null;
	}
}

Dazu definieren wir uns einen eigenen zweiten Datentypen:

class MyOtherDataType
{
	public int Wert { get; set; }
	public static implicit operator MyDataType(MyOtherDataType toConvert)
	{ return new MyDataType() { Wert = Convert.ToString(toConvert.Wert) }; }

	public static implicit operator string(MyOtherDataType toConvert)
	{
		if (toConvert != null)
			return Convert.ToString(toConvert.Wert);
		else
			return "toConvert is null";
	}
}

Ich habe jetzt allerdings die Konvertierungen zu SecureString rausgenommen, und implizite Konvertierungen zwischen diesen beiden Datentypen hinzugefügt. Prinzipiell muss nach dem Keyword “operator” lediglich der Zieldatentyp angegebene werden, und als Methodenparameter der Quelldatentyp. Das kann man so vereinfachen:

public static implicit/explicit operator Zieldatentyp (Quelldatentyp pInstance) { Konvertierungsimplementation }

Die Main Methode unserer Konsolenanwendung sieht demnach dann so aus:

static void Main(string[] args)
{
	MyDataType myTypeInstance = new MyDataType() { Wert = "hallo" };
	Log("myTypeInstance: " + myTypeInstance);

	//ist null, da "hallo" kein gültiger int ist
	MyOtherDataType myOtherTypeInstance = myTypeInstance;
	Log("myOtherTypeInstance nach ungültigem int in myTypeInstance: " + myOtherTypeInstance);

	myTypeInstance.Wert = "10";
	myOtherTypeInstance = myTypeInstance;
	Log("myOtherTypeInstance nach gültigem int in myTypeInstance: " + myOtherTypeInstance);
	Console.Read();
}

“Warum verwendet der jetzt nicht “string.Format” zum Logging?!” werden sich wohl einige Fragen. Die Antwort ist einfach: string.Format(“Text”, parameter) erwartet als parameter “objects”. Würden wir nun unsere Instanz “myOtherTypeInstance” übergeben, würde unsere Typumwandlung nach string nicht ausgeführt werden… :)
Damit sollte es jetzt eigentlich klar sein wie die eigene Konvertierungen direkt innerhalb eines Datentyps implementiert werden können.

TypeConversions – Oder: Wer bist du denn?

Wir haben eine eigene Klasse welche unseren Datentyp darstellt. Diese möchten wir jetzt mit ein paar Typkonvertierungen versehen, allerdings nicht über Extension methods oder andere ausgelagerte Funktionen. Unser Datentyp sieht wie folgt aus:

class MyDataType
{
	public string Wert { get; set; }
}

Der Einfachheit halber nur eine simple Property. Normalerweise hat man ja sehr viele Unterschiedliche Properties in seinem Datentyp. Wir möchten jetzt daraus z.B. einen SecureString machen, oder auch einfach den Wert loggen. Man könnte jetzt natürlich einfach schreiben:

MyDataType myTypeInstance = new MyDataType() { Wert = "hallo" };

Log(myTypeInstance.Wert);

Wobei unsere Logfunktion einen string-Parameter entgegennimmt der dann – wo auch immer – protokolliert wird.

Diese Verwendung ist aber spätestens bei komplexeren Datentypen einfach nur unhandlich. Deshalb wollen wir einfach schreiben können:

Log(myTypeInstance);

oder um daraus einen SecureString zu machen:

SecureString mySecString = (SecureString)myTypeInstance;

Die meisten Implementierungen (die ich bisher gesehen habe) setzen auf eine extra ausgelagerte Funktion oder Extension methods. Die ausgelagerten Funktionen haben einen Nachteil: Sie konvertieren einfach mal alles was man reinwirft. Dieses Verhalten haben wir aber eigentlich nicht bezweckt, oder wir wollen das einfach nicht, da wir ja beim Zeitpunkt des Codens die Verwendungsmöglichkeiten einfach nicht alle im Überblick haben kann oder auch nicht testen kann.
Gehen wir also über Extension methods. OK – damit haben wir im Idealfall schonmal den Basistyp eingegrenzt. Nachteil allerdings hier: Extension methods funktionieren erst ab .Net 3.5… Also stehen wir wieder am Anfang.

Wie gehen wir also nun an die Sache ran?
Die Lösung ist liegt auf der Hand und ist eigentlich so simpel, das es mich immer wieder verwundert das es so selten eingesetzt wird:
Innerhalb unseres Datentyps definieren wir zwei neue statische Funktionen zur Typumwandlung hinzu:

public static implicit operator string(MyDataType toConvert) { }

public static explicit operator SecureString(MyDataType toConvert) { }

Diese beiden Funktionen führen nun unsere gewünschten impliziten wie expliziten Konvertierungen aus.
Wir füllen diese beiden Methoden also wie folgt auf:

public static implicit operator string(MyDataType toConvert)
{
	return toConvert.Wert;
}
public static explicit operator SecureString(MyDataType toConvert)
{
	SecureString mySecureString = new SecureString();
        foreach (char item in toConvert.Wert)
	{
		mySecureString.AppendChar(item);
	}
	return mySecureString;
}

Das heißt, dadurch gewinnen wir saubereren Code und klarere Strukturen, da die Umwandlungen direkt innerhalb der Typen definiert werden können. Sie sind also genau da, wo sie auch hingehören.
Das komplette Testprogamm hier:

class MyDataType
{
	public string Wert { get; set; }
	public static implicit operator string(MyDataType toConvert)
	{
		return toConvert.Wert;
	}
	public static explicit operator SecureString(MyDataType toConvert)
	{
		SecureString mySecureString = new SecureString();
		foreach (char item in toConvert.Wert)
		{
			mySecureString.AppendChar(item);
		}
		return mySecureString;
	}
}

class Program
{
	static void Main(string[] args)
	{
		MyDataType myTypeInstance = new MyDataType() { Wert = "hallo" };
		Log(myTypeInstance);
		SecureString mySecString = (SecureString)myTypeInstance;
		Log(mySecString.ToString());
		Console.Read();
	}
	static void Log(string message)
	{
		Console.WriteLine(message);
	}
}