LINQ Extension Methods: Intersect

by wolfgang@gehirnwindung.de (Wolfgang) Mai 19, 2010 20:25

Mit der Intersect-Erweiterungsmethode vergleicht man zwei Sequenzen des gleichen Typs miteinander und erhält als Ergebnis eine neue Sequenz, in der die Elemente der ersten Sequenz vorhanden sind, die auch in der 2. Sequenz vorkommen (Gegenteilig zur Except-Erweiterungsmethode). Dabei wird jedes Element nur einmal aufgeführt. Verglichen wird - wenn man nichts anderes angibt - mit EqualityComparer<T>.Default.

Ohne Intersect Extension Method kann man das Verhalten in etwa so nachbilden

private List<T> Intersect<T>( List<T> list1, List<T> list2, EqualityComparer<T> comparer ) {
    var ret = new List<T>( );
    if( comparer == null ) comparer = EqualityComparer<T>.Default;

    for( int i = 0; i < list1.Count; i++ ) {
        if(
            !ret.Contains( list1[i], comparer ) &&
            list2.Contains( list1[i], comparer )
        ) {
            ret.Add( list1[i] );
        }
    }

    return ret;
}

Der Aufruf mit Erweiterungsmethode kann so erfolgen

list1.Intersect( list2 );
list1.Intersect( list2, EqualityComparer<T>.Default );

Tags: , ,

C#

LINQ Extension Methods: Except

by wolfgang@gehirnwindung.de (Wolfgang) Mai 12, 2010 21:00

Mittels Except-Erweiterungsmethode vergleicht man zwei Sequenzen des gleichen Typs miteinander und erhält als Ergebnis eine neue Sequenz, in der die Elemente der ersten Sequenz vorhanden sind, die in der 2. Sequenz nicht vorkommen. Dabei wird jedes Element nur einmal aufgeführt. Verglichen wird - wenn man nichts anderes angibt - mit EqualityComparer<T>.Default.

Ohne Except Extension Method kann man das Verhalten in etwa so nachbilden

private List<T> Except<T>( List<T> list1, List<T> list2, EqualityComparer<T> comparer ) {
    var ret = new List<T>( );
    if( comparer == null ) comparer = EqualityComparer<T>.Default;

    for( int i = 0; i < list1.Count; i++ ) {
        if(
            !ret.Contains( list1[i], comparer ) &&
            !list2.Contains( list1[i], comparer )
        ) {
            ret.Add( list1[i] );
        }
    }

    return ret;
}

Doch zum Glück gibt es die Erweiterungsmethoden, der Aufruf erfolgt in etwa so

list1.Except( list2 );
list1.Except( list2, EqualityComparer<T>.Default );

Tags: , ,

C#

LINQ Extension Methods: Union

by wolfgang@gehirnwindung.de (Wolfgang) Mai 12, 2010 01:00

Die Erweiterungsmethode Union bietet die Möglichkeit, 2 Sequenzen des gleichen Typs zu einer so zusammenzuführen, dass von jedem Element nur noch eine Entsprechung übrig bleibt. Damit verhält sich Union wie der Aufruf von Concat().Distinct().

Die Reihenfolge der Elemente in der Ergebnissequenz ergibt sich aus der Reihenfolge innerhalb der einzelnen Listen, die nacheinander abgearbeitet werden.

Ohne Union - Extension Method sieht das in etwa so aus

private List<T> Union<T>( List<T> list1, List<T> list2, EqualityComparer<T> comparer ) {
    var result = new List<T>( );
    if( comparer == null ) comparer = EqualityComparer<T>.Default;
    for( int i = 0; i < list1.Count; i++ ) {
        if( !result.Contains( list1[1], comparer ) ) {
            result.Add( list1[i] );
        }
    }

    for( int i = 0; i < list2.Count; i++ ) {
        if( !result.Contains( list2[1], comparer ) ) {
            result.Add( list2[i] );
        }
    }

    return result;
}

Der Vergleich wird - wenn nicht anders angegeben - mit dem Default - EqualityComparer<T> vollzogen.

Genauso verhält sich auch die Union-Erweiterungsmethode, die wie folgt aufgerufen wird

list1.Union( list2 );
list1.Union( list2, EqualityComparer<T>.Default );

Tags: , ,

C#

LINQ Extension Methods: Zip

by wolfgang@gehirnwindung.de (Wolfgang) Mai 11, 2010 18:51

Mit der Zip-Erweiterungsmethode können die Elemente zweier Sequenzen 1:1 zusammengeführt werden. Das Ergebnis ist eine neue Sequenz.
Zip ist somit eine spezialisierte Form von Join, wobei der Index als Key verwendet wird. Daher ist es auch egal, wie viele Elemente die jeweiligen Listen haben - zurückgegeben werden nur diejenigen Elemente, die in beiden Listen vorkommen.

Angenommen, man hat die 2 Listen firstNames und lastNamens mit den Vor- bzw. Nachnamen und möchte diese miteinander so kombinieren, dass eine string-Sequenz herauskommt, in der diese Namen durch Komma getrennt zusammengesetzt stehen. Die beiden Listen sind entsprechend sortiert.

Ohne Zip-Methode würde das in etwa so aussehen

var fullNames = new List<string>();
int count = Math.Min( firstNames.Count, lastNames.Count );
for( int i=0; i<count; i++ ){
    fullNames.Add( string.Format( "{1}, {0}", firstNames[i], lastNames[i] ) );
}

wobei die 2. Zeile vereinfacht werden kann, wenn sichergestellt ist, dass die beiden Listen gleich viele Elemente enthalten.

Mit der Zip-Methode geht das wesentlich kürzer (und ist meiner Meinung nach auch etwas leichter lesbar).
Wer noch Probleme mit den Lambda-Ausdrücken hat, der kann sich mit dem Artikel Tanz den => Lambda mit mir... ein wenig einlesen.

var fullNames = firstNames.Zip(
                    lastNames,
                    ( firstName, lastName ) => string.Format( "{1}, {0}", firstName, lastName )
                );

Der Rückgabewert ist nicht auf string-Sequenzen beschränkt. Es kann eine beliebige Klasse (z.B. eine anonymen Klasse) angegeben werden.

Z.B. ist fullNames hier

var fullNames = firstNames.Zip(
                    lastNames,
                    ( firstName, lastName ) => new {
                                                     FirstName = firstName,
                                                     LastName = lastName
                                               }
                );

eine Sequenz von Klassen, in denen die Eigenschaften FirstName und LastName gesetzt sind.

Tags: , ,

C#

The Type initializer for '{0}' threw an exception - oder warum die Reihenfolge statischer Felder nicht egal ist...

by wolfgang@gehirnwindung.de (Wolfgang) April 29, 2010 18:06

Heute hab ich in einem Projekt meine Klassen etwas aufgeräumt und dabei auch die Reihenfolge der Methoden, Eigenschaften und Variablen angepasst. Der Build des Projekts war erfolgreich - die Tests danach aber nicht. Es erschien der Fehler:

The Type initializer for '{0}' threw an exception.

Da ich mir aber sicher war, dass ich wirklich nur Elemente auf Klassenebene verschoben hab, hat mich das wirklich sehr verwundert... Aus dem StackTrace konnte man entnehmen, dass es sich um den statische Konstruktor (.cctor()) handelt. Nun, ich schreibe oft statische Konstruktoren, aber in aller Regel ohne jeglichen Inhalt, weswegen der Grad der Verwunderung dementsprechend stieg.

Kurzum, der Fehler liegt wirklich an der Reihenfolge, allerdings geht es hierbei ausschließlich um statische Felder, die in der richtigen Reihenfolge definiert sein müssen. Bei genauerer Betrachtung ist das auch nur logisch. Ich hätte mir an der Stelle eine schöne Kompiler-Fehlermeldung gewünscht.

Ein einfaches Beispiel zeigt, worum es genau geht.

public class Test {
    private static readonly decimal x = 1 / y;
    private static readonly int y = 8;

    static Test() { }
}

Sobald eine Instanz der Klasse erstellt wird, kommt es zu einer Division durch 0 (bzw. zum Versuch desselben) und damit zu besagter Ausnahme.
Zur Veranschaulichung hier noch das Äquivalent. y ist bei der ersten Verwendung noch mit dem default-Wert belegt - und der ist nunmal 0.

public class Test {
    private static readonly decimal x;
    private static readonly int y;

    static Test() {
        x = 1 / y;
        y = 8;
    }
}

Damit ist aber auch klar, dass man die Initialisierung statischer Variablen innerhalb des statischen Konstruktors selbst vornehmen sollte - zumindest wenn diese von anderen Variablen abhängen.

Tags:

C#

URL Rewrite und der SiteMap-Provider (Request.PathInfo)

by Wolfgang Kluge Mai 13, 2009 20:03

Heute wollte ich einer meiner Seiten so umstellen, dass URL-Rewrite verwendet wird. Das ging auch recht schnell (entsprechende Links gesetzt und über Request.PathInfo die Parameter ausgelesen), allerdings stellte sich beim Testen dann heraus, dass ich ein Problem mit dem Menü hatte. Dieses konnte nun nicht mehr die aktuelle Seite markieren... Das Menü wird über eine Sitemap erstellt.

Verkürzt sieht der Code für das Menü so aus:

<asp:SiteMapDataSource ID="MenuSource" runat="server" SiteMapProvider="menu" />
<asp:Menu ID="mnu" runat="server" DataSourceID="MenuSource">

In meiner Sitemap sind die entsprechenden URLs eingetragen. Z.B.

<siteMapNode title="Startseite" url="~/default.aspx" />
<siteMapNode title="Projekte" url="~/projects.aspx"/>
<siteMapNode title="Eigene Projekte" url="~/projects.aspx/own"/>

Wird die Seite "/projects.aspx" aufgerufen, so wird der Eintrag "Projekte" entsprechend gekennzeichnet (mittels CSS-Class "AspNet-Menu-Selected"). Das hat auch noch mit "/projects.aspx?test=1" wunderbar funktioniert. Wird dagegen die Seite "/projects.aspx/test" aufgerufen, wird der entsprechende Eintrag nicht mehr gefunden, da mit Request.RawURL verglichen wird. Und da steht "/projects/test" drin.

Es ist also nur verständlich, dass der Provider den Eintrag nicht findet und dementsprechend kein Menüeintrag als aktiv gekennzeichnet werden kann. Erschwerend kommt hinzu, dass durchaus auch mal ein Treffer dabei sein kann (wie in diesem Beispiel beim Aufruf von "/projects.aspx/own").

Da hilft nur selbst den richtigen Eintrag suchen. Das ist natürlich nur nötig, wenn Request.PathInfo gefüllt ist.

protected override void OnInit( EventArgs e ) {
    base.OnInit( e );
    if( MenuSource.Provider.CurrentNode == null && !string.IsNullOrEmpty( Request.PathInfo ) ) {
        MenuSource.Provider.SiteMapResolve += new SiteMapResolveEventHandler( SiteMap_SiteMapResolve );
    }
}

private SiteMapNode SiteMap_SiteMapResolve( object sender, SiteMapResolveEventArgs e ) {
    SiteMapProvider provider = sender as SiteMapProvider;
    if( provider == null ) return null;
    return provider.FindSiteMapNode( e.Context.Request.AppRelativeCurrentExecutionFilePath );
}

Sobald MenuSource.Provider.CurrentNode == null ist und in Request.PathInfo etwas steht, wird auf das Ereignis SiteMapResolve des Providers reagiert. Dort wird nach einem Eintrag im aktuellen SiteMapProvider gesucht, der nur dem Pfad (ohne PathInfo-Angaben) entspricht.

Wenn man das Menü innerhalb eines Controls hat (wo es hingehört *g*), muss der Code auch nur 1x angegeben werden.

Tags: , ,

ASP.NET

Die 2 Fragezeichen ?? ... und die null

by Wolfgang Kluge Mai 03, 2009 13:26

Nicht unbekannt - aber dennoch nicht gerade oft verwendet - fristet der c#-Operator ?? sein dasein. Zu Unrecht? Lässt sich doch mit diesem recht einfach testen, ob in einer Objektvariable ein Objekt oder null steht und entsprechend etwas anderes zurückgeben. In dem Beispiel

string test1 = null, test2 = "irgendwas";
Trace.WriteLine( test1 ?? "(null)" );
Trace.WriteLine( test2 ?? "(null)" );

wird - wie man sich bestimmt schon denken kann - zuerst "(null)" und dann "irgendwas" ausgegeben. Oder zu Deutsch: Es wird der linke Operand zurückgegeben, falls dieser nicht null ist, ansonsten wird der rechte Operand zurückgegeben.

Damit wird einem c#-Entwickler eine sehr einfache und leserliche Möglichkeit gegeben, auf null-Werte zu reagieren. Selbstverständlich kann man mit dieser Schreibweise auch "Schindluder" betreiben und den Code so unleserlich wie nur irgend möglich gestalten. Wenn man das aber nicht vorhat, warum dann nicht verwenden?

vollständigen Artikel anzeigen...

Tags:

C#

C# Datentypen

by wolfgang@gehirnwindung.de (Wolfgang) April 30, 2009 19:30

Insgesamt 15 Datentypen können direkt mit C#-Keywords beschrieben werden. Diese Schlüsselwörter sind dabei "nur" Aliasnamen für entsprechende .NET-Framework-Datentypen.

Übersicht

Die meisten Datentypen sind Werttypen (siehe struct). Diese können den angegebenen Werte annehmen. Dagegen können Verweistypen wie string und object (und alle Klassen) zusätzlich den "Wert" null annehmen. Will man nun, dass auch ein Werttyp null annehmen kann, so kann man auf die Nullable-Typen zugreifen.

Keyword .NET-Datentyp Speicher Wertebereich Bemerkung
bool System.Boolean 8 bit
1 byte
true oder false (0xFF oder 0x00)  
byte System.Byte 8 bit
1 byte

0x0 bis 0xFF
0 bis 255

 
char System.Char 16 bit
2 byte
0x0 bis 0xFFFF (ein Unicode-Zeichen)
0 bis 65.535
einzelnes Unicode-Zeichen innerhalb einfachen Anführungsstrichen (')
decimal System.Boolean 128 bit
16 byte
(-7,9x1028 bis 7,9x1028) / 10x10(0 bis 28) Suffix M
double System.Double 64 bit
8 byte
-1,79769313486232x10308 bis 1,79769313486232x10308 Suffix D
float System.Single 32 bit
4 byte
-3,402823x1038 bis 3,402823x1038 Suffix F
int System.Int32 32 bit
4 byte

0x8000 0000 bis 0x7FFFFFFF
-2.147.483.648 bis 2.147.483.647

 
long System.Int64 64 bit
8 byte

0x8000 0000 0000 0000 bis 0x7FFF FFFF FFFF FFFF
-9.223.372.036.854.775.808 bis 9.223.372.036.854.775.807

Suffix L
object System.Object      
sbyte System.SByte 8 bit
1 byte

0x80 bis 0x7F
-128 bis 127

 
short System.Int16 16 bit
2 byte
0x8000 bis 0x7FFF
-32.768 bis 32.767
 
string System.String     Kein oder mehrere Unicode-Zeichen innerhalb doppelten Anführungsstrichen (") - oder null.
uint System.UInt32 32 bit
4 byte
0x0 bis 0xFFFF FFFF
0 bis 4.294.967.295
 
ulong System.UInt64 64 bit
8 byte
0x0 bis 0xFFFF FFFF FFFF FFFF
0 bis 18.446.744.073.709.551.615
Suffix UL (U, L)
ushort System.UInt16 16 bit
2 byte
0x0 bis 0xFFFF
0 bis 65.535
 

vollständigen Artikel anzeigen...

Tags:

C#

Powered by BlogEngine.NET 1.6.1.6
Theme by Mads Kristensen | Modified by Mooglegiant and me ;)