Archiv

Artikel Tagged ‘C#’

NDepend 2020.1

Als ich vor kurzem gefragt wurde, ob ich meinen Test der neuen NDepend Version erneuern möchte, stimmte ich gern zu. Diese Software bezeichnet sich selbst als ein „Schweizer Taschenmesser für .NET und .NET Core Entwicklung Teams“. Es bietet vielfältige Möglichkeiten, um ein Projekt auf Code-technische Probleme und technische Schulden zu untersuchen und das auch über einen zeitlichen Verlauf in grafischer Form darzustellen bzw. die konkreten Veränderungen zu ermitteln.

Die Installation ist einfach und die Integration in Visual Studio ist optional, schnell gemacht und für mich ein echter Mehrwert, da ich bereits während der Entwicklung schnellen Zugriff auf alle relevanten Funktionen habe. Obwohl NDepend dann regelmäßig automatisch Analyseergebnisse sammelt, merkt man davon während der normalen Arbeit in Visual Studio nichts. Das ist bei Visual Studio Erweiterungen anderer Hersteller leider nicht immer der Fall.

NDepend Dashboard

Mit einem einfachen Klick auf einen großen Kreis erhält man eine kleine Übersicht, kann eine Analyse von Hand starten oder einfach ins Dashboard wechseln. Dort erhält man eine Einschätzung der Projektqualität. Das basiert auf einem sehr ausgefeilten Regelwerk, welches der Hersteller entwickelt hat. Ich habe das Regelwerk nicht selbst geändert. Ich habe schließlich das Projekt der Code-Analyse wieder aufgegriffen, weil ich das Tool als neutralen und unbestechlichen Auditor meiner Softwareprojekte sehe, der wesentlich mehr Erfahrung als ich in diesem Bereich hat. Die Darstellung des Dashboards beinhaltet eine anpassbare Übersicht. Bestandteil sind einmal Diagramme über die Entwicklung der Kennzahlen. Weiter ist, im Besonderen nach der Auswahl einer Vergleichsbasis, eine Darstellung der Projektbewertung jetzt und im direkten Vergleich zu finden. Das schließt eine nach Schweregrad gruppierte Übersicht der Anzahl der Vorfälle ein. Ein Klick auf diese Zahl bringt ein sofort in eine konkrete Übersicht der Vorfälle bzw. Probleme. Dies ist besonders dann hilfreich, wenn man eine Entwicklung einer Kennzahl vielleicht nicht so erwartet hat und man nun die Ursache ergründen möchte.

NDepend issue list

Hier beginnt jetzt aber auch der anspruchsvolle Teil! Man kann sehr leicht die einzelnen Vorfälle heraussuchen und bekommt die Fundstellen sogar angezeigt, wenn Sie nur der Vergleichsbasis zu finden waren, nun also entfernt oder korrigiert wurde. Ferner sieht man die Definition des Vorfalls in einer LINQ ähnlichen Sprache, mit einer ausführlichen Erklärung und weiteren externen Verweisen. Die Beschreibungen sind sehr gut, prinzipbedingt aber gerade am Anfang keine leichte Kost! Hier muss man schon einen festen Willen haben, das persönliche Niveau zu verbessern. Keine Verständnisprobleme bei englischen Texten sind auch hilfreich. Dann findet man in NDepend jedoch einen idealen Partner.

Ein Prunkstück, auf das der Hersteller besonders stolz ist, ist der Dependency Graph. Der Name ist Programm! Grenzen scheint es für dieses Modul nicht wirklich zu geben. Man kann mit einer sehr hohen Geschwindigkeit das Zusammenspiel der eigenen oder externen Klassen, Namensbereichen oder Funktionen anschauen und durch viele, oft sehr sinnvoll vorbelegte Optionen, übersichtlich gestalten. Das kann sehr hilfreich bei der Sichtung oder Umgestaltung von Codebereichen sein. Die Leistungsklasse wird eindrucksvoll durch die Präsentation der .NET Core 3 Klassen gezeigt. In meinen eigenen aktuellen Projekten ist mir die Struktur jedoch extrem vertraut. Darum ist steht bei mir das Modul nur in der zweiten Reihe, für viele andere Entwickler wird das sicher nicht gelten.

Kommen wir noch zur Einschätzung des Preises. Dieser liegt ungefähr im Bereich der JetBrains-Tools. Da der Kundenkreis merklich kleiner sein wird, ist das bezogen auf den Funktionsumfang mehr als fair. Positive muss hier erwähnt werden, dass NDepend einer permanenten Entwicklung unterliegt und man trotzdem nicht den Eindruck bekommt, dass die Fertigstellung erst beim Kunden geschieht. Das obligatorische Subscription-Model ist damit gerechtfertigt. Im Vergleich zu meinen älteren Versionen empfinde ich beispielsweise die Integration innerhalb Visual Studio wesentlich umfangreicher und runder.

Fazit: Vermisst habe ich lediglich die Kombination der Analyse mit einer Versionsverwaltung wie SVN oder Git. Das ist schade und stellt sicher noch ein großes Potential für die Zukunft dar. Mein persönliches Highlight von NDepend bleibt ganz klar die große Menge an vordefinierten Regeln. Diese sind sauber definiert, gut gruppiert, flexibel anpassbar und vor allen Dingen gut mit den passenden Hilfethemen verbunden.

Große Aufzählungen im .NET Framework

Neulich wurde ich mit der Definition einer besonders umfangreichen Aufzählung konfrontiert. Da hierfür die magische 32Bit Grenze überschritten wurde, war ich skeptisch. Ein Test ergab, das diese Skepsis in der Tat berechtigt war. Folgende Deklaration:

public enum Big
{
    Small = 1 << 1,
    Medium = 1 << 8,
    Large = 1 << 40
}

erzeugt dann das Problem. Bis hier:

var enumTest = Big.Small;
Debug.Assert(enumTest == Big.Small);
enumTest = Big.Medium;
Debug.Assert(enumTest == Big.Medium);
enumTest = Big.Large;
Debug.Assert(enumTest == Big.Large);

erscheint alles in Orndung, ein Ausgabetest:

var enumTest = Big.Small;
Console.WriteLine(enumTest);
enumTest = Big.Medium;
Console.WriteLine(enumTest);
enumTest = Big.Large;
Console.WriteLine(enumTest);

zeigt aber dann das Problem. Die Definition Medium und Large sind intern identisch:

Small
Medium
Medium

Zur Lösung des Problems muss man zwei Änderungen durchführen. Einmal ist der Aufzählungstyp von long abzuleiten und die Basiszahl für die Bitverschiebung muss ebenfalls eine long-Zahl sein:

public enum Big: long
{
    Small = 1L << 1,
    Medium = 1L << 8,
    Large = 1L << 40
}

Das Ergebnis bestätigt die korrekte Ausführung. Bei der Nutzung von 64 Bit ist aber dann vorerst wirklich Schluss!

KategorienMicrosoft .NET Tags: ,

Pfadnamen für Ausgabe kürzen

15. Dezember 2011 Keine Kommentare

Im Microsoft .NET Framework ist schon lange eine Funktion zum Kürzen von Dateinamen enthalten. Aus c:\Windows\System32\MyDll.dll kann dann zum Beispiel c:\Windows\…\MyDll.dll werden. Hierzu greift man auf TextRenderer.MeasureText zurück. Allerding hat diese Funktion einen kleinen Fehler bei dem zurückgegebenen Wert, der hier beschrieben ist:

Der Workaround ist zum Glück auch gleich angegeben, und kann dann so aussehen:

string filename = string.Copy(filenameForOutput);
if (!string.IsNullOrEmpty(filename))
{   
   TextRenderer.MeasureText(filename, Font, Size,
     TextFormatFlags.PathEllipsis | TextFormatFlags.ModifyString);
   filename = filename.Substring(0, filename.IndexOf('\0'));
}

Der Source zeigt auch gleich noch eine kleine andere Ergänzung: Die Funktion modifiziert den übergebenen String. Man sollte diesen also vorher kopieren, sofern man nicht schon mit einer Kopie des Strings arbeitet.

KategorienMicrosoft .NET Tags: ,

Vererbung von Properties entfernt die Attribute

Für eine Erweiterung von meinem Quellcode hatte ich geplant, ein paar Properties mit Attributen zu versehen. Dabei sollten die Properties virtual sein. Die Abfrage der Attribute sollte dann mittels GetCustomAttributes geschehen. Leider hat das gar nicht funktioniert, was auch schon andere .NET-Programmierer feststellen mussten. Darum habe ich die Problemstellung extrahiert:

Zum Prüfen der Attribute wurde folgende Funktion verwendet:

static void CheckAttribute(MemberInfo memberInfo)
{
    var attributes = memberInfo.GetCustomAttributes(true);
    if (attributes.Length == 0) return;
 
    foreach (object attribute in attributes)
        Console.WriteLine("The type of the attribute in {1} is {0}.", 
            attribute, memberInfo.Name);
}

Weiterhin wurden zwei Klassen geschrieben, wo einmal eine abgeleitete Methode und eine abgeleitete Propertie mit Attributen versehen wurde:

public abstract class BaseClass
{
    [TestAttribute]
    public virtual String TestProperty { get; set; }
 
    [TestAttribute]
    public virtual void TestMethod() { return; }
}
public class DerivedClass : BaseClass
{
    public override string TestProperty { get; set; }
    public override void TestMethod() { base.TestMethod(); }
}
 
public class TestAttribute : Attribute
{
}

Zu guter Letzt wurde das ganze überprüft:

Console.WriteLine("BaseClass:");
CheckAttribute(typeof(BaseClass).GetProperty("TestProperty"));
CheckAttribute(typeof(BaseClass).GetMethod("TestMethod"));
 
Console.WriteLine("DerivedClass:");
CheckAttribute(typeof(DerivedClass).GetProperty("TestProperty"));
CheckAttribute(typeof(DerivedClass).GetMethod("TestMethod"));

Das Ergebnis fiel überraschend aus:

BaseClass:
The type of the attribute in TestProperty is TestAttrib.TestAttribute.
The type of the attribute in TestMethod is TestAttrib.TestAttribute.
DerivedClass:
The type of the attribute in TestMethod is TestAttrib.TestAttribute.

Wähernd die Methode TestMethod in beiden Klassen als mit dem Attribut ausgezeichnet, erkannt wurde, war dies bei der Eigenschaft TestProperty nicht der Fall. Um die Ursache zu finden, war ein Blick in die IL notwendig. Hier die Umsetzung der BaseClass, welche das Attribute in beiden Fällen korrekt definiert.

.method public hidebysig newslot virtual instance void TestMethod() cil managed
{
    .custom instance void TestAttrib.TestAttribute::.ctor()
}
 
.property instance string TestProperty
{
    .get instance string TestAttrib.BaseClass::get_TestProperty()
    .set instance void TestAttrib.BaseClass::set_TestProperty(string)
    .custom instance void TestAttrib.TestAttribute::.ctor()
}

Die Ableitung war aber überaschend umgesetzt. Während die Methode offensichtlich überschrieben wurde, wurde die Propertie quasi neu angelegt, aber ohne dem zugewiesenen Attribut.

.method public hidebysig virtual instance void TestMethod() cil managed
{
}
 
.property instance string TestProperty
{
    .get instance string TestAttrib.DerivedClass::get_TestProperty()
    .set instance void TestAttrib.DerivedClass::set_TestProperty(string)
}

Damit war das Ergebnis der Untersuchung: GetCustomAttributes hat definitionsgemäß auch die Ableitungen untersucht. Jedoch verlieren sich die Attribute beim Überschreiben von Properties. Eine Erklärung dafür habe ich bis jetzt nicht gefunden, ich konnte den Effekt aber in .NET 2.0 – 4.0 nachstellen.

Erweiterung von INotifyPropertyChanged (PostSharp)

17. März 2011 4 Kommentare

Mit PostSharp bekommt man eine mächtige Möglichkeit, Code mittels Attributen aufzupeppen. So wird man auch in die Lage versetzt, wiederkehrenden Code zu vermeiden. Üblicherweise sieht nämlich beispielsweise eine Implementation von System.ComponentModel.INotifyPropertyChanged wie folgt aus:

public class Test: System.ComponentModel.INotifyPropertyChanged
{
    private string _myProp;
    public string MyProp
    {
        get { return _myProp; }
        set
        {
            if (_myProp == value) return;
            _myProp = value;
            OnPropertyChanged("MyProp");
        }
    }
 
    public event PropertyChangedEventHandler PropertyChanged;
 
    public void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Verwendet man jedoch PostSharp als Referenz in seinem Projekt und die Klasse NotifyPropertyChangedAttribute aus

{ProgramFiles}\PostSharp 2.0\Samples\.NET Framework 3.5\CSharp\NotifyPropertyChanged

kann man seinen Code merklich verkürzen:

[NotifyPropertyChanged]
public class TestBase
{
    public string MyProp { get; set; }
}

Problem
Leider ergibt letztere Implementation das Problem, dass ich die Kontrolle über das Auslösen von Events vollständig an das Attribut NotifyPropertyChanged abgebe. Da der Code für das Event nachträglich in das Compilat injiziert wird, ergibt sich keine Möglichkeit, das Event PropertyChanged manuell auszulösen, da .NET das Auslösen von Events nur innerhalb der implementierenden Klasse erlaubt. Versuche, Zugriff auf das Event durch Vorimplementierung des Interfaces zu erhalten, helfen leider auch nicht, da der durchs Attribut erzeugte Code in einer Property der Klasse zusätzlich angelegt wird. Dies kann man leicht mit dem Reflektor prüfen:

public string MyProp
{
    [CompilerGenerated]
    get
    {
        return this.<get_MyProp>();
    }
    [CompilerGenerated]
    set
    {
        LocationInterceptionArgsImpl<string> CS$0$1 = new LocationInterceptionArgsImpl<string>(this, Arguments.Empty);
        CS$0$1.Location = <>z__Aspects.l3;
        CS$0$1.TypedBinding = <MyProp>c__Binding.singleton;
        CS$0$1.TypedValue = value;
        this.<>z__aspect0.OnPropertySet(CS$0$1);
    }
}

Das Setzen der per Attribut erweiterten Property wird beispielsweise komplett auf z__apect0 umgeleitet. Und an genau dieses z__apect0 ist ja zur Entwurfszeit noch nicht verfügbar (das ist ja der Sinn des Attributes).

Umgehen konnte ich das Problem mit einer kleiner Erweiterung des NotifyPropertyChangedAttribute:

[ImportMember("ManualPropertyChanged", IsRequired = false,
		Order = ImportMemberOrder.AfterIntroductions)]
public Event<PropertyChangedEventHandler> ManualPropertyChanged;
 
public override void RuntimeInitializeInstance()
{
	base.RuntimeInitializeInstance();
	if (ManualPropertyChanged != null)
		ManualPropertyChanged.Add((sender, args) => 
		{ if (PropertyChanged != null) PropertyChanged(Instance, args); });
}

Bietet meine Klasse, welche das NotifyPropertyChangedAttribute ausweist, folgendes Ereignis an:

public event PropertyChangedEventHandler ManualPropertyChanged;

so abonniert das Attribut dieses Ereignis und sorgt beim Auslösen von ManualPropertyChanged automatisch für eine Weiterleitung an das Ereignis PropertyChanged.
kick it on dotnet-kicks.de

KategorienMicrosoft .NET Tags: ,

RSA Signierung von Strings

19. März 2010 Keine Kommentare

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

KategorienMicrosoft .NET Tags: , ,

Roulette, ein sicheres System?

23. Januar 2010 1 Kommentar

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)

KategorienAllgemein, Microsoft .NET Tags:

Blog-Parade: Die 3 beliebtesten Fachbücher aus dem .NET-Umfeld

28. September 2009 Keine Kommentare

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.

KategorienMicrosoft .NET Tags: ,

ReSharper und Probleme mit Codevervollständigung

Seit geraumer Zeit zeigte die Code-Vervollständigung unter C# folgendes Bild:
Codevervollständigung für IF

Dabei sind viele unnütze if-Kombination zu sehen, welche sich auf vorhandene Namespace-Definitionen beziehen. Nach längerer Suche konnte ich den Verursacher ausmachen. Ich verwende in den betroffenen Projekten VistaDB. In der Version 3.5 build 84 wurde die Library mittels Obfuscation unleserlich gemacht. Leider hat genau diese Verschlüsselung der Bibliothek den Effekt, dass jedes nicht öffentliche Objekt in einen eigenen neuen Namespace gelegt wird, hier also beispielsweise:

if
If
IF
@if

Diese Namespace-Definitionen bietet der Resharper per Code-Vervollständigung. Leider steht sowohl seitens VistaDB als auch seitens JetBrains eine Lösung noch aus.

Master Detail mit List<>

Möchte man Listen (System.Collection.Generic.List<>) an Comboboxen mittels einer Master-Detail-Beziehung anbinden, so bietet sich die hier dargestellte Vorgehensweise an.

Code der Listen:

    public class Detail
    {
        public String Caption { get; set; }
        public int Id { get; set; }
 
        public Detail(string caption)
        {
            Caption = caption;
        }
    }
 
    public class DetailList: List<Detail>
    {
        public String DetailListCaption { get; set; }
 
        public DetailList(string masterCaption)
        {
            DetailListCaption = masterCaption;
        }
    }
 
    public class Master: List<DetailList>
    { }

Zur Anwendung wird die Liste bespielhaft befüllt:

    private Master master = new Master();
...
    master.Add(new DetailList("Cap Master 1"));
    master.Add(new DetailList("Cap Master 2"));
 
    master[0].Add(new Detail("Cap Detail 1.1"));
    master[0].Add(new Detail("Cap Detail 1.2"));
    master[1].Add(new Detail("Cap Detail 2.1"));
    master[1].Add(new Detail("Cap Detail 2.2"));

Nun werden noch zwei BindingSource-Komponenten erzeugt und initialisiert:

    masterBindingSource = new BindingSource {DataSource = typeof (Master)};
    comboBox1.DataSource = masterBindingSource;
    comboBox1.DisplayMember = "DetailListCaption";
 
    detailBindingSource = new BindingSource {DataSource = typeof (DetailList)};
    comboBox2.DataSource = detailBindingSource;
    comboBox2.DisplayMember = "Caption";
 
    masterBindingSource.CurrentChanged += masterBindingSource_CurrentChanged;
...
    private void masterBindingSource_CurrentChanged(object sender, EventArgs e)
    {
        detailBindingSource.DataSource = masterBindingSource.Current;
    }

Wichtig ist dann nur noch die Initialisierung der Datasource mit einem konkreten Wert:

    masterBindingSource.DataSource = master;
KategorienMicrosoft .NET Tags: ,