Beim Prüfen von Quelltext sind mir wiederholt Ungenauigkeiten mit double Zahlen aufgefallen, die schon in der 8. Stelle auftraten. Nachdem ich Ausgeschlossen hatte, dass irgendwo Single-Konvertierungen stattfinden, habe ich das Problem genauer unter die Lupe genommen. Konkret ist mir aufgefallen, dass die double Rechnung 4.0 * 25.4 eigentlich 101.6 ergeben müsste, in der Zielvariable stand aber 101.59999847412109. Extern nachstellbar war das Problem anfangs nicht.
Reproduzieren ließ sich das Verhalten erst mit folgender Funktion:
private void TestDouble()
{
var pres = new Microsoft.DirectX.Direct3D.PresentParameters
{
Windowed = true,
SwapEffect = Microsoft.DirectX.Direct3D.SwapEffect.Discard,
AutoDepthStencilFormat = Microsoft.DirectX.Direct3D.DepthFormat.D16
};
new Microsoft.DirectX.Direct3D.Device(
0, Microsoft.DirectX.Direct3D.DeviceType.Hardware, this,
Microsoft.DirectX.Direct3D.CreateFlags.SoftwareVertexProcessing, pres);
double value = 4;
double value2 = value * 25.4;
Debug.Assert(101.6 == value2);
}
Das Erzeugen der 3D-Welt mittel Managed DirectX setzt standartmäßig die Genauigkeit von Double-Zahlen auf einfache Genauigkeit (entspricht Single). Nachdem diese Ursache gefunden war, fand ich hier:
http://blogs.msdn.com/b/tmiller/archive/2004/06/01/145596.aspx
schnell eine Problemlösung. Wenn man mit leichten Geschwindigkeitseinschränkungen leben kann, ist nur eine kleine Änderung beim Erzeugen vom Device notwendig:
new Microsoft.DirectX.Direct3D.Device(
0, Microsoft.DirectX.Direct3D.DeviceType.Hardware, this,
Microsoft.DirectX.Direct3D.CreateFlags.SoftwareVertexProcessing |
Microsoft.DirectX.Direct3D.CreateFlags.FpuPreserve, pres);
Der zusätzliche Parameter Microsoft.DirectX.Direct3D.CreateFlags.FpuPreserve sorgt dafür, dass DirectX die Genauigkeit der Double-Zahlen so lässt, wie das Standardmäßig der Fall ist.
Bezüglich der Verarbeitung von Komma-Werten habe ich heute wieder einge Erkenntnisse gewonnen, die teilweise auch aus der verbesserten Dokumentation vom Microsoft.NET Framework 4 her rührten. Dort ist unter anderem zum Typ Double folgendes zu lesen:
Bei Verwendung einer Gleitkommazahl könnte ein Wert möglicherweise nicht wiederhergestellt werden.
Das Problem verdeutlicht folgendes gekürztes Beispiel aus der MSDN:
double fromLiteral = -4.42330604244772E-305;
double fromParse = Double.Parse("-4.42330604244772E-305");
Console.WriteLine("Double value from literal: {0,29:R}", fromLiteral);
Console.WriteLine("Double value from Parse method: {0,24:R}", fromParse);
// On 32-bit versions of the .NET Framework, the output is:
// Double value from literal: -4.42330604244772E-305
// Double value from Parse method: -4.42330604244772E-305
//
// On other versions of the .NET Framework, the output is:
// Double value from literal: -4.4233060424477198E-305
// Double value from Parse method: -4.42330604244772E-305
Das die hinteren Stellen einer Double-Zahl Ungenauigkeiten unterliegen, war mir schon länger klar. Das der selbe Code auf verschiedenen Systemen aber andere Genauigkeiten erzielen kann, war eine neue Erkenntnis, die bis dato nur als ungeäußerte und nicht beweisbare Vermutung im Raum stand.
Um das Problem zu umgehen, habe ich zwei Extensions für mich geschrieben:
public static bool NearZero(this double value)
{
return Math.Abs(value) < 1e-12;
}
public static bool NearValue(this double value, double compareValue)
{
if (value.NearZero() && compareValue.NearZero()) return true;
return Math.Abs(value - compareValue) <
Math.Min(Math.Abs(value), Math.Abs(compareValue)) * 1e-12;
}
Mit der ersten prüfe ich auf 12 Stellen hinter dem Komma, ob die Zahl fast Null ist. Mit der zweiten Methode kann ich simple 2 Werte auf annähernde Gleichheit überprüfen.
Mittels des Attributes SerializableAttribute ist es sehr einfach, komplexe Objekte auf Festplatte zu speichern und später erneut zu laden. Allerdings gibt es einige Fallen, die man dabei kennen sollte:
1. Kommerzielle .NET-Anwendungen werden gern durch Codeverschlüsselung geschützt. Das bereitet natürlich Probleme, wenn solche Objekte auf Festplatte gespeichert werden. Eine spätere Verschlüsselung hat evtl. den Namen der Properties wieder geändert. Abhilfe kann hier das ObfuscationAttribute verschaffen. Damit kann man einzelne Code-Teile von der Verschlüsselung ausschließen. Damit ist der Code zwar nicht mehr vor fremden Blicken geschützt, aber das Laden/Speichern von Klassen klappt dann auch Versionsübergreifend.
2. Refaktorierung von Klassen: Klassen, die serialisiert werden, dürfen auf keinen Fall umbenannt werden. Gleiches gilt natürlich für deren Properties und Unterklassen.
Leider hat der Hersteller von VistaDB den Vertrieb dieser Datenbank eingestellt. Als kleinen Trost konnte man zum Abschluss noch den Sourcecode sehr günstig erwerben und mit diesem auch eine unverschlüsselte Version ohne Lizenzabfragen herunterladen.
Im Folgenden möchte ich den somit letzten Umstieg von VistaDB.Net 3 auf 4.1, die bei mir noch in einigen Projekten aktiv ist, zeigen:
1. Installation von VistaDB.Net 4.1. Diese Version 4.1 ließ sich problemlos parallel zur Version 3 installieren und nutzen.
2. Upgraden der Datenbank auf die 4er Version. Das geht im VistaDB Data Builder im Menü Database unter dem Punkt Upgrade VDB3 Database sehr einfach. Leider ändert sich die Dateiendung von .vdb3 auf .vdb4. Setup-Skripte müssen hier evtl. auch angepasst werden.
3. Öffnen der Projekte in Microsoft Visual Studio. In den Projekten ist ein Verweis auf VistaDB.NET20 vorhanden. Dieser wird entfernt und durch den VistaDB 4 Provider ersetzt.
4. Die Datasets (.xsd) müssen in einem externen Editor geöffnet werden, um den XML-Code direkt zu bearbeiten. Man findet darin mindestens einmal Provider=”VistaDB.NET20″, was durch Provider=”System.Data.VistaDB” ersetzt werden muss.
5. Jetzt kommt der schwierigste Teil. Die Connectionstrings müssen aktualisiert werden und die Datasets nochmal neu geschrieben werden. Dazu reicht es eigentlich, den Connectionsstring in den Einstellungen auf die neue Datei (die Dateiendung hat sich ja geändert) zu setzen und einen TableAdapter im Dataset neu zu konfigurieren (ohne Änderung). Jedoch war bei mir das Problem, dass die ConnectionsStrings nicht mehr gültig waren. Das ist zwar ein Problem von Visual Studio und nicht von VistaDB, es steht hier aber trotzdem im Weg. Darum gehe ich einen anderen Weg. Darum habe ich einen anderen Weg gewählt.
Ich habe mir den Namen des Connectionstrings notiert und ihn aus den Einstellungen gelöscht. Dann habe ich ein neues Dataset erzeugt und dort den Connectionstring neu eingegeben und unter dem alten Namen gespeichert. Damit wurde dann als Dummy ein Tableadapter konfiguriert. Das Dataset kann zum Schluss wieder gelöscht werden. Nun können die alten Datasets geöffnet und ein Tableadater neu erzeugt werden.
Dieses Wochenende fand in Karlsruhe der .NET Open Space Süd 2010 statt. Für mich war es das erste Mal, das ich an so einer Veranstaltung teilgenommen habe. Die Veranstaltung wurde in den Räumen von bluehands ausgerichtet.
Die Themen wurden durch die ca. 50 Teilnehmer selbst bestimmt. Dabei legte sich automatisch ein großer Fokus auf die Schnittstelle zwischen Kunden und Entwickler. Hier stecken wohl für alle die größten Probleme bzw. es ist einfach der meiste Handlungsbedarf. Es gab einige Sessions in Richtung BDD und DDD. Diese Sessions waren sehr informativ und es fand eine angeregte Diskussion ab. Ein Fazit der Veranstaltung war für viele, dass wir Entwickler mit Tools für die Entwicklung selbst, gut versorgt sind. Wo es mangelt, ist eine sinnvolle Unterstützung durch Software, Anforderungen des Kunden zu erfassen und umzusetzen bzw. deren Umsetzung zu prüfen.
Ein Highlight der Veranstaltung war ein kleines DoJo, initiert durch Ilker. Thema war die Umsetzung von arabischen Zahlen in das römische Zahlensystem.
Im Rahmen eines Projektes wollte ich ursprünglich Strings mittels eines asymmetrischen Verschlüsselungsverfahrens verschlüsseln. Hierzu wählte ich als Verfahren RSA. Nachdem ich alles soweit fertig hatte, ist mir leider aufgefallen, dass RSA mittels des privaten Schlüssels nur entschlüsseln kann, nicht jedoch verschlüsseln. Um trotzdem Nutzen aus der Sache zu ziehen (es ging nur um Prüfung von Daten), nutze ich den privaten Schlüssel nun zur Erzeugung einer Datensignatur. Die Anwendung des Anwenders kann diese mittels des öffentlichen Keys problemlos überprüfen.
Da es recht aufwendig ist, die geeigneten konkreten Klassen und Funktionen zur Erzeugung und Speichern der Schlüssel sowie zur Anwendung der Signierung herauszusuchen, habe ich eine kleine Demo-Anwendung geschrieben. Die jeweils benötigten Eingabefelder wurden farbig passend hervorgehoben.
Benötigt wird zuerst der passende Namespace:
using System.Security.Cryptography;
Die Erzeugung der Schlüssel ist extrem einfach:
var rsa = new RSACryptoServiceProvider();
textBoxPrivateKey.Text = rsa.ToXmlString(true);
textBoxPublicKey.Text = rsa.ToXmlString(false);
Die ToXmlString-Funktion erzeugt ein sehr gut speicherbares Format der Schlüssel, dass so auch wieder gut eingelesen werden kann. Wichtig ist aber, die Signatur in ein übertragungstaugliches Format zu bringen, da diese standardmäßig aus Bytes besteht. Hierzu nutze ich eine Hilfsfunktion BytesToHex:
public static string BytesToHex(byte[] bytes)
{
var hexString = new StringBuilder(bytes.Length);
for (int i = 0; i < bytes.Length; i++)
{
hexString.Append(bytes[i].ToString("X2"));
}
return hexString.ToString();
}
private void ButtonSignTextClick(object sender, EventArgs e)
{
var rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(textBoxPrivateKey.Text);
var encryptedSymmetricKey = rsa.SignData(
Encoding.Unicode.GetBytes(textBoxSourceText.Text),
new SHA1CryptoServiceProvider());
textBoxSignature.Text = BytesToHex(encryptedSymmetricKey);
}
Ist die Signatur erstellt, kann sie auf ähnliches Weg überprüft werden.
public static byte[] StringToByteArray(String hex)
{
int numberChars = hex.Length;
var bytes = new byte[numberChars / 2];
for (int i = 0; i < numberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
private void ButtonTestSignClick(object sender, EventArgs e)
{
var rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(textBoxPublicKey.Text);
byte[] signText = StringToByteArray(textBoxSignature.Text);
byte[] originalText = Encoding.Unicode.GetBytes(textBoxSourceText.Text);
bool signOk = rsa.VerifyData(originalText, new SHA1CryptoServiceProvider(), signText);
MessageBox.Show("Signature OK: " + signOk);
}
Hier kann das ganze noch als VS2008-Anwendung mit einer ausführbaren Datei zusammen heruntergeladen werden: RSA Source
In letzter Zeit findet man immer wieder Berichte, wie man Online-Casinos angeblich mit einem todsicherem System austricksen könnte. Das soll wie folgt funktionieren:
Man setzt einen Euro auf eine Farbe. Gewinnt man, fährt man mit einem Euro fort. Verliert man, so setzt man das doppelte und zwar solange, bis man wieder gewinnt. Der Verlust wird dadurch vollständig ausgeglichen und ist auch unter dem Begriff Martingal Roulette System bekannt.

Das klingt erst mal gut, funktioniert auch theoretisch. Allerdings gibt es ein paar praktische Probleme: Tischlimits führen schnell dazu, dass man seine Strategie nicht fortsetzen kann. Der Gewinn pro Runde bleibt bei einem Euro stehen. Man muss also 200 erfolgreiche Runden absolvieren, für einen Gewinn von 200 Euro…
Ich habe dazu eine kleine Anwendung geschrieben, die das ganze durchtestet. Die einzelnen Parameter sind einstellbar, ob man gewinnt oder nicht, wird dem Zufall überlassen. Die Anwendungen läuft, bis entweder die maximale Rundenzahl erreicht wurde, oder man den Einsatz mangels Kapital nicht mehr bringen kann. Das geht oft schneller, als gewünscht. Am Ende siegt eben immer noch die Bank:
Downloads:
Ausführbare Anwendung (10 kByte), Microsoft .Net 2.0 erforderlich
Quellcode für C# 2.0 (18 kByte)
Möchte man existierende Objekte in Microsoft.NET klonen, so steht scheinbar die Schnittstelle System.ICloneable zur Verfügung. Dazu wird dann oft zur Implementation der Methode MemberwiseClone geraten. In der Praxis zeigt sich leider, das Referenzen somit aber nicht geklont werden.
Eine Lösung wird durch Serialisierung angeboten:
http://www.csharp411.com/c-object-clone-wars/ (Punkt 4)
Auf diesem Weg kann ein Datenabzug eines Objekts erstellt werden und auf dieser Basis kann eine Kopie (Clone) erzeugt werden. Eine Demonstration eines CloneManagers habe ich hier dargestellt.
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace ConsoleApplication6
{
public static class CloneManager
{
public static T Clone<T>(T instance) where T : class
{
var serializer = new BinaryFormatter();
var stream = new MemoryStream();
serializer.Serialize(stream, instance);
stream.Seek(0, SeekOrigin.Begin);
return serializer.Deserialize(stream) as T;
}
}
[Serializable]
public class A
{
public int C { get; set; }
public B B { get; set; }
public A()
{
B = new B();
}
}
[Serializable]
public class B
{
public int[] Ints { get; set; }
public B()
{
Ints = new[] {2, 4, 5};
}
}
class Program
{
static void Main(string[] args)
{
var a = new A {C = 3};
var a2 = CloneManager.Clone(a);
a.B.Ints[1] = 3;
Console.WriteLine("a.C {0} - a2 {1}", a.C, a2.C);
Console.WriteLine("a.B [0] {0} - a2.B [0] {1}", a.B.Ints[0], a2.B.Ints[0]);
Console.WriteLine("a.B [1] {0} - a2.B [1] {1}", a.B.Ints[1], a2.B.Ints[1]);
Console.ReadKey();
}
}
}
Der CloneManager ist an diese Version http://www.codeproject.com/KB/cs/CloneManager.aspx angelehnt.
Mit diesem Beitrag möchte ich mich der Blog-Parade zum Thema der beliebtesten Fachbücher aus dem NET-Bereich anschließen.
1. Richtlinien für das Framework-Design
Dieses Buch enthält Richtlinien für die Namensgebung im .NET-Bereich. Außerdem enthält man Einblicke, wie speziell die Konventionen innerhalb des .NET-Frameworks herausgearbeitet wurden. Nach dem Durcharbeiten des Buches fällt es leichter, Namensfehler zu vermeiden und die Benennung geschickter zu wählen.
2. C# 3.0 Entwurfsmuster
Das Buch ist eine Einführung in Entwurfsmuster. Vielen andere Einführungen, welche nicht speziell auf die Spracheigenschaften von C# ausgelegt sind, wirken komisch, wenn am Ende des Kapitels beispielsweise klar wird, welche bekannte C#-Technik dahinter steckt.
3. Grundlagen der Anwendungsentwicklung mit dem .NET Framework 2.0
Dieses Buch setzt vernünftiges Grundwissen voraus und vermittelt einen darauf basierenden breiten Querschnitt über die vorhandenen Technologien und deren Anwendung. Es zeigt das .NET Framework 2.0 aus der Sicht, wie es von Microsoft konzipiert wurde und wie es eingesetzt werden sollte.
Verwendet man Formular-Authentifizierung, so lässt sich der TimeOut von Asp.NET Anwendungen scheinbar nicht über 20min erhöhen. Damit es doch geht, muss in der web.config der TimeOut explizit vermerkt werden:
...
<authentication mode="Forms">
<forms name="WebFormlogin" loginUrl="WebFormlogin.aspx" timeout="60">
...