Archive for the ‘.NET’ Category

ViewModelek kérdésköre

Wednesday, March 3rd, 2010

Mostanában oktatok és prototípust írok, közben ezer design kérdést tisztázok a fejemben. Az egyik ilyen pl., hogy a ViewModel DependencyProperty vagy INotifyPropertyChanged módon közvetítse a változásokat a GUI-ra?
Én az INotifyPropertyChanged-re szavaznék, mert így a ViewModel és nem függ a GUI technológiától, eleve nekem fura a modellben pl. WPF fogalmakat látni.

Infók a kérdéskörben.

Általános áttekintés:
INotifyPropertyChanged vs. DependencyProperty in ViewModel

Egy POCO szavazat:
View Models: POCOs versus DependencyObjects

Én Expressionnel oldom meg, hogy ne legyenek property name stringek a kódban, ő nem, de az objektum pool ötlet tetszik benne, átveszem, de szemeteljünk.
A base class which implements INotifyPropertyChanged

Nagyon dicsérik a videót, még nem volt időm megnézni.
Jason Dolinger on Model-View-ViewModel

Jó kép az MVVM rétegekről.

Egyféle Validation megközelítés.
Using a ViewModel to Provide Meaningful Validation Error Messages

VS 2010 RC elszállás - hotfix

Saturday, February 20th, 2010

Ez igencsak bosszantó volt, remélem a fix megoldja.
Nekem nem tablet pcm van, mégis előjön.
https://connect.microsoft.com/VisualStudio/Downloads/DownloadDetails.aspx?DownloadID=26662

EF SSDL alapú felhasználói bemenet ellenőrzés

Monday, February 1st, 2010

A következőn töröm a fejem. Az Entity Framework SSDL-jében definiálva vannak az entitás property-k alapvető jellemzői: nullázhatóság, max hossz. Ezeket a GUI-n ki kell kényszeríteni. Nyilván vannak összetettebb validálási szabályok, de most koncentráljunk ezekre az elemiekre.
Utálok minden redundanciát egy rendszerben, ezért azt gondoltam, a szabályokat kiolvasom az EF sémájából, és ebből táplálom meg a validáló részeket, így nem kell törődni az egyszerű validálásokkal, automatikusan működni fognak.

A következő kis kódocska mutatja meg a metaadatok használatát:


o.ForceLoadingSchemas();

var sspaceEntitySets = o.MetadataWorkspace
       .GetItems<EntityContainer>(DataSpace.SSpace)
       .First().BaseEntitySets.OfType<EntitySet>();

foreach (EntitySet es in sspaceEntitySets)
{
    foreach (EdmProperty p in es.ElementType.Properties)
    {
        ReadOnlyMetadataCollection<Facet> facets = p.TypeUsage.Facets;
        Debug.WriteLine("{0} is {1} nullable", p.Name, (bool)facets["Nullable"].Value ? "" : "not");
        if (facets.Contains("MaxLength"))
        {
            Debug.WriteLine("{0} MaxLenght is {1}", p.Name, (int)facets["MaxLength"].Value);
        }
        Debug.WriteLine("{0} is {1} nullable", p.Name, (bool)facets["Nullable"].Value ? "" : "not");
    }
}

A ForceLoadingSchemas az ObjectContext partial classában van:


public void ForceLoadingSchemas()
{
    CreateQuery<BusinessEntity>("AdventureWorks2008Entities3.BusinessEntities").ToTraceString();
}

Csinált már valaki ilyet? Van benne valami csapda, amit most nem látok?

SQLCLR deplyment hiba

Wednesday, January 27th, 2010

Error: Incorrect syntax near valami.
Akkor jön elő, ha az SQLCLR assemblyt és benne a függvényeket akarja az VS deployolni. Több oka lehet, most az volt, hogy egy .NET oldalon double-t visszaadó függvény véletlenül így lett deklarálva:


[SqlFunction(..., TableDefinition = "Datum datetime, Szazalek double")]

Mi a hiba benne? SQL Serverben nincs double, csak real és float. Ráadásul a C# float az az SQL real és a C# double az SQL Server float (kb.). :)

Az előbbi helyesen:


[SqlFunction(..., TableDefinition = "Datum datetime, Szazalek float")]

Miért kellett SQLCLR függvényt írni? A futó aggregálások (én legalábbis nem tudok jobbat kurzor nélkül) o(n2)-es algoritmusok, ezt CLR-ben könnyen meg lehet írni o(n)-re. Pl:


using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public class UserDefinedFunctions
{
    internal class Result
    {
        public long RowId;
        public double CumDollarGain;
        public double TopDollarGain;
        public double DollarDrawDown;
    }

    //Input table: create table #trades(RowId long, DollarGain money, other columns possible)
    [SqlFunction(FillRowMethodName = "FillRow", DataAccess = DataAccessKind.Read,
        TableDefinition = "RowId bigint, CumDollarGain float, TopDollarGain float, DollarDrawDown float")]
    public static IEnumerable Cumul()
    {
        using (var conn = new SqlConnection("Context connection=true"))
        {
            using (var cmd = new SqlCommand("select RowID, DollarGain from #trades", conn))
            {
                var res = new List<Result>();
                conn.Open();
                double cumulPrice = 0, topPrice = 0, drawDawn = 0;

                using (SqlDataReader r = cmd.ExecuteReader())
                {
                    int idCol = r.GetOrdinal("RowID");
                    int gainCol = r.GetOrdinal("DollarGain");

                    while (r.Read())
                    {
                        var price = r.GetDouble(gainCol);

                        cumulPrice += price;
                        topPrice = Math.Max(price, topPrice);

                        drawDawn += price;
                        drawDawn = Math.Min(drawDawn, 0);

                        res.Add(new Result
                                    {
                                        RowId = r.GetInt64(idCol),
                                        CumDollarGain = cumulPrice,
                                        TopDollarGain = topPrice,
                                        DollarDrawDown = drawDawn
                                    });
                    }
                    return res;
                }
            }
        }
    }
    public static void FillRow(object obj,
    out long id,
    out double cumDollarGain,
    out double topDollarGain,
    out double dollarDrawDown)
    {
        var r = (Result)obj;
        id = r.RowId;
        cumDollarGain = r.CumDollarGain;
        topDollarGain = r.TopDollarGain;
        dollarDrawDown = r.DollarDrawDown;
    }
};

Sajnos nem lehet átpasszolni a megnyitott SqlDataReadert a két metódus között, ezért kénytelen az ember letárolni az eredményhalmazt. Persze pár ezer sornál ez nem gond.

Jó Entity Framework architektúra cikkek

Tuesday, January 19th, 2010

Hogyan építsünk EF-re többrétegű appot?
1. Anti-Patterns To Avoid In N-Tier Applications
2. N-Tier Application Patterns
3. Building N-Tier Apps with EF4

Jó cikkek.

Entity Framework gyors lett

Wednesday, January 6th, 2010

Na, ez aztán a furcsa. Pár napja írtam, mennyire lassan indul az EF. Nem tudom mitől, talán attól, hogy felraktam a Microsoft ADO.NET Entity Framework Feature Community Technology Preview 2-t, ami lecserélte a bugos .NET 4 Beta2-es EF assemblyket, amelyek nem használták ki az előfordított viewkat? Nem tudom, nem nyomoztam utána, mert most jó. :)
Korábban annyira felbosszantott, hogy elkezdtem nézni az NHibernate-et, de ahhoz meg a LINQ támogatás jár még gyerekcipőben, nekem meg tele van azzal a programom. Így az kiesett.
Marad az EF, így már nagyon szeretem.

Entity Framework teljesítmény-optimalizálás

Sunday, January 3rd, 2010

Boldog Új Évet.

Ez az Entity Framework egy nehezen betörhető ló. A run-time teljesítménye jó, de az indulása, az katasztrófa.
Pár hasznos link a témában.

Alapelvek 1, 2.

Hol találhatunk fogást rajta?.
Viewk előre generálása:

Régi módon.
Eszköz hozzá: EdmGen.

Új módon:
How to use a T4 template for View Generation

Nagy modellek esetén hogyan ne őrüljünk meg (meg fogunk) 1, 2, gyakorlati példa.

Szűrés opcionális paraméterekre LINQ-val

Tuesday, December 8th, 2009

A feladvány, hogy van sok szűrési feltétel, ezekre szűrni kell, ha van érvényes értékük, vagy kihagyni a szűrésből, ha nincs.
A feladatra SQL Server esetén bool algebrás megoldást írnék:


select * from tabla
where
(@p1 is null) or (@p1 = col1)
and
(@p2 is null) or (@p2 = col2)
...
option(recompile)

Az option hint azért kell, mert így a nullos paraméterek teljesen kiesnek a tervből, csak a valódi szűrésekre készül terv. Ez szerintem óriási dolog, érdemes észben tartani.

Ugyanezt a logikát linqval is el lehet játszani, ref típusokkal kb. így:


var x = from z in Valami
where (p1 == null) || (p1 = z.col1)
&&
(p2 == null) || (p2 = z.col2)

Egy másik megoldásban azt használjuk ki, hogy késleltetett módon értékelődnek ki a kifejezések, így lehet őket láncolni. Itt látható ez a megoldás, és még más megközelítések is.

A harmadik megoldásban írhatunk egy saját szűrő operátort is erre a célra. Az előbbi címről:


public static IQueryable<TSource> WhereIf<TSource>(
    this IQueryable<TSource> source, bool condition,
    Expression<Func<TSource, bool>> predicate)
{
    if (condition)
        return source.Where(predicate);
    else
        return source;
}

Nehéz megindokolni, melyik megoldás a jobb.

Patterns for Parallel Programming: Understanding and Applying Parallel Patterns with the .NET Framework 4

Tuesday, December 1st, 2009

120 oldalas kis olvasmány, alig várom már, hogy legyen egy kis időm elolvasni.

Az tetszik a 4.0-ban, hogy végre megint hozzányúltak az alapokhoz, mint pl. a threadinghez.

Managed memory leak nyomozás WinDBG-vel

Monday, November 30th, 2009

Imádom a WinDbg-t, mondtam már? Az egyik kis programom módszeresen eszegette a memóriát. Ciklusban végez feldolgozást, rettentő sok adattal, és ezek egy része szépen bennragadt a memóriában. .NET-ben nincs memory leak a klasszikus értelemben, de van oly módon, hogy a rootokból marad referencia egyes objektumokra, így azok élve maradnak, szándékaink ellenére. Néha azonban nem triviális kinyomozni, melyik root miatt ragadt be valami a memóriába.
A VSTS profiler segítségével odáig el lehet jutni, hogy ki ragad be a memóriába. Az Object LifeTime nézetben az Instances alive at end oszlopra rendeztetve láthatjuk, kik maradnak élve a program végén. Én beraktam egy force-olt GC-zést a program végére, így aki ezek után még benn maradt, azok egy része szándékaim ellenére tette ezt, ezért azt leaknek tekintem, és meg kell szüntetni.
Jöhet a WinDbg. File, Open Executable, F5. A program végén a GC után raktam még egy Console.ReadLine()-t. Amikor ide eljut, a debuggerben CTRL-Breakkel megállítom a program futását (várakozását). Betöltöm az sos-t:
.load C:\Windows\Microsoft.NET\Framework64\v4.0.21006\sos.dll

Kilistáztatom a GC heapen levő ojjektumokat:
!DumpHeap -stat


000007ff005c2278     2766       221280 System.Data.Metadata.Edm.TypeUsage
000007fef1b90d00      370       222136 System.Byte[]
000007fef1b89be0    13379      1330896 System.String
000007fef1b8c8a8     1125      1447552 System.Int32[]
000007fef1b3bef0     9298      3224392 System.Object[]
000007ff00274988   245954     29514480 ATS.Bar

Akinek nem szabadna már a memóriában lenni, az az ATS.Bar objektumok, 245954 darab, 29514480 bájt méretben. Nézzük megy a példányokat belőle:

!DumpHeap -type ATS.Bar


0000000004952c60 000007ff00274988      120
0000000004952cd8 000007ff00274988      120
0000000004952d50 000007ff00274988      120
0000000004952dc8 000007ff00274988      120
0000000004952e40 000007ff00274988      120
0000000004952eb8 000007ff00274988      120
total 0 objects
Statistics:
              MT    Count    TotalSize Class Name
000007ff00fb38a0        1           24 System.Collections.Generic.GenericEqualityComparer`1[[ATS.BarCollectionDescriptor, Common]]
000007ff00271050        1           24 ATS.BarDAL
000007ff00270e68        1           48 ATS.BarFactory
000007ff00fb31b8        1           88 System.Collections.Generic.Dictionary`2[[System.Int32, mscorlib],[System.Collections.Generic.List`1[[ATS.BarFromTickFactory, Common]], mscorlib]]
000007ff00fb27a8        1           88 System.Collections.Generic.Dictionary`2[[ATS.BarCollectionDescriptor, Common],[ATS.BarFromTickFactory, Common]]
000007ff00275378        1           88 System.Collections.Generic.Dictionary`2[[ATS.Symbol, Common],[ATS.BarCollectionByInterval, Common]]
000007ff00fb3ee0        1           96 System.Collections.Generic.Dictionary`2+Entry[[System.Int32, mscorlib],[System.Collections.Generic.List`1[[ATS.BarFromTickFactory, Common]], mscorlib]][]
000007ff00fb3b10        1           96 System.Collections.Generic.Dictionary`2+Entry[[ATS.BarCollectionDescriptor, Common],[ATS.BarFromTickFactory, Common]][]
000007ff00fb0498        1           96 System.Collections.Generic.Dictionary`2+Entry[[ATS.Symbol, Common],[ATS.BarCollectionByInterval, Common]][]
000007ff00fb2d30        3          120 System.Collections.Generic.List`1[[ATS.BarFromTickFactory, Common]]
000007ff00790990        3          120 ATS.BarCollectionDescriptor
000007ff00fb1568        7          168 System.Collections.ObjectModel.ObservableCollection`1+SimpleMonitor[[ATS.Bar, Common]]
000007ff00886530        3          240 ATS.BarFromTickFactory
000007ff00fb1648        7          280 System.Collections.Generic.List`1[[ATS.Bar, Common]]
000007ff00273788        7          952 ATS.BarCollection
000007ff00274988   245954     29514480 ATS.Bar
Total 245993 objects

A kis 120 bájtos izék a problémásak (a végén az összefoglaló azért tartalmaz több típust is, mert substring szűrést csinál a -type). Az első oszlop az egyedi objektumok címe, a második a típus metódusleíró táblája.
És most jön a lényeg. Ki miatt érhető el a rootokból mondjuk az utolsó Bar példány?

!GCRoot 0000000004952eb8


DOMAIN(00000000003CF530):HANDLE(Pinned):1217d8:Root:  0000000012657048(System.Object[])->
  0000000002bf7f28(System.Collections.Generic.Dictionary`2[[System.Int32, mscorlib],[System.Collections.Generic.List`1[[ATS.BarFromTickFactory, Common]], mscorlib]])->
  0000000002bf94b8(System.Collections.Generic.Dictionary`2+Entry[[System.Int32, mscorlib],[System.Collections.Generic.List`1[[ATS.BarFromTickFactory, Common]], mscorlib]][])->
  0000000002f06ca0(System.Collections.Generic.List`1[[ATS.BarFromTickFactory, Common]])->
  0000000002f06cc8(System.Object[])->
  0000000002f05860(ATS.BarFromTickFactory)->
  0000000004952eb8(ATS.Bar)

Ebben az látszik, az első bejegyzésből, amit még a CLR startup hozzott létre hogyan lehet eljutni a beragadt objektumunkhoz. A CLR generikus neveket C#-ra visszafordítva az látszik, hogy egy Dictionary> hivatkozik egy Dictionary.Entry>-re, az a dictionary belső tárolóeleme. Ez rámutat egy List-re, ami továbbmutat egy ATS.BarFromTickFactory-ra, ami hivatkozik a Bar-ra.

Ez alapján már rekonstruálható a probléma forrása. A probléma elemi oka, hogy túlzásba vittem a statikusok használatát, és nem figyeltem a takarításukra.


static readonly Dictionary<BarCollectionDescriptor, BarFromTickFactory> FactoriesByDesc =
    new Dictionary<BarCollectionDescriptor, BarFromTickFactory>();

Ebből rendesen kiszedtem a tárolt BarFromTickFactory példányt, ha már nem volt rá szükség. De elfeledkeztem róla, hogy volt egy másik kollekció is:


public static readonly Dictionary<int, List<BarFromTickFactory>> FactoryListBySymbolId =
    new Dictionary<int, List<BarFromTickFactory>>();

Ebből viszont nem szedtem ki a hivatkozás az adott BarFromTickFactory-ra, így az szépen beragadt a memóriába.

Tanulságok:
1. Szeretjük a WinDbt-t.
2. Kerüljük a statikusokat. Ha a BarFromTickFactory példányokat egy mások osztály példányai tárolnák, akkor ha azok kifutnak a szkópból, automatikusan a GC martalékai lesznek. A sok statikus sok odafigyelést igényel, kár erőltetni őket.

Apró objektumok szemetelése

Saturday, November 28th, 2009

Az előző bejegyzéshez kapcsolódik még az alábbi. Sok felesleges memóriaallokállást és aztán GC-zést okoztam az alábbi kóddal:


...
OnBarArrived(new BarArrivedEventArgs(bar));
...

        private void OnBarArrived(BarArrivedEventArgs e)
        {
            if (BarArrived != null)
            {
                BarArrived(this, e);
            }
        }

Ha a BarArrived event null, azaz nem iratkozott fel senki az eventre, akkor feleslegesen hozok létre egy BarArrivedEventArgs-ot. A javított verzió így néz ki:


private void OnBarArrived(Bar bar)
{
    if (BarArrived != null)
    {
        BarArrived(this, new BarArrivedEventArgs(bar));
    }
}

A bar objektum már úgyis kész van, az eventargot viszont csak akkor hozom létre, hogy tényleg szükség van rá.

Naív ojjektumhasználat - sok szemét

Friday, November 27th, 2009

Két hete elkezdtem intenzíven használni a Visual Studio 2010-et, mivel szükségem volt a MemoryMappedFiles-ra a 4.0-s fwből. Ezt shared memoryként használom, amivel a laptopon kb. 1GByte/sec-kel tudok adatokat másolni két processz között, így kiváló cache-t tudtam építeni a segítségével. De most nem erről lesz szó.
A profilert is jelentősen továbbfejlesztették, van benne pl. rendes memória allokálás követés (lehet, hogy a régi is tudta ezt, akkor pardon).
Nézzük az alábbi képet:

A SecondInterval.Day property-t közel 6 milliószor hívtam meg, minden híváskor előállítva egy új ojjektumot, 140MByte-nyi szemetet hagyva magam után.

A naív implementáció így nézett ki:


public static SecondInterval Day
{
  get { return new SecondInterval(86400); }
}

Maga a típus egy egyszerű Whole Value pattern implementáció, egy sima immutable objektum, ráadásul class, így referenciális típus.

Nem memóriapazarló módon így néz ki a property:


private static readonly SecondInterval aDay = new SecondInterval(86400);
public static SecondInterval Day
{
    get { return aDay ; }
}

A readonly fontos, hogy nehogy valaki kiüsse a referenciámat, és berakjon egy sunyi másik időt reprezentáló ojjektumot a napi helyére. Az ojjektum immutable, ez fontos, másként semmit nem érne a readonly, a gyomrát lehetne piszkálgatni.
Amin még el lehetne gondolkodni, hogy struktúrává, value type-pá átalakítani a típust, csak akkor meg minden helyen ahol használom, másolni kellene az értékét. Mivel ez most belül egy 32 bites int, és 64 bit alatt futtatom, ahol a referenciák 64 bitesek, a másolással még mindig jobban járok. Úgyhogy lehet, hogy struct lesz, de előbb megnézem classként hogy muzsuikál.

Pár fejlettebb GC dolog

Wednesday, October 14th, 2009

Ezeket érdemes megnézni, érdekesek.

Ha nem szeretnénk, hogy egy rövid, de teljesítményzabáló kódunkat megszakítsa a GC: Latency Modes és Low-Latency GC in .NET 3.5.

Ha interopolunk, és várunk visszahívást nem managed oldalról, de félünk, hogy a GC felszabadítja az ojjektumunkat visszahívás előtt: GC.KeepAlive.

Ha nagyon sok memóriát zabáló appot írunk, és nem szeretnénk belefutni OutOfMemoryException-be, inkább lassítanánk a feldolgozáson: MemoryFailPoint.

Garbage Collection Notifications in .NET 3.5 SP1.

Jó Garbage Collector cikkek

Wednesday, October 14th, 2009

1, 2, 3, 4. Nem csak a szokásos, közismert dolgok, hanem mélyebbek is.
Pl. tudod-e, mekkora memóriát kér el induláskor a GC az OS-től? Hogy 3-féle GC is van (2-t ismer a legtöbb ember, már, aki egyáltalán hallott róla, hogy többféle GC is létezik)? Vagy miért száll el általában egy x86-os .net app 1.5G körüli memóriafelhasználásnál? Stb.

Random orderby Linq és EF használatával

Monday, October 12th, 2009

Az SQL Serveren szokásos orderby newid() szerveroldali megoldást csak linq to SQL esetén tudjuk kihasználni, szerveroldali függvénnyel. Az EF-ben az ilyesmi nem megy:


... orderby Guid.NewID() ...

Ez az EF verzió még nem tudja lefordítani szerveroldali kifejezéssé az orderby kifejezését.
Viszont a random rendezést át lehet nyomni kliensoldalra, ha a lekérdezést “materializáluk” (AsEnumerable) előbb:


Random rnd = new Random();
(from s in ATSEntities.Instance.Symbol
select s).AsEnumerable().OrderBy(o => rnd.Next()));

Nem guidot használtam, hanem Randomot, az kisebb költségű, és az én célomra nem baj, ha csak pszeudo-random a sorrend.

Code Coverage for Concurrency

Thursday, October 8th, 2009

Mostanában rengeteg cikket olvasok az msdnből, rám fér, mert minden hónapban hozza a postás, de párat már ki se nyitottam. Szívom fel magam az oktatások közötti szünetekben, kicsit képbe kerüljek azokkal a cuccokkal, amik a 4.0-ban jönnek be.
A fenti cikk nagyon tetszett, arról ír, hogyan lehet megközelíteni egy többszálú app tesztelését.

Sandcastle és társai

Wednesday, September 30th, 2009

Az NDoc halott, van helyette SandCastle. Nagyon jó, imádom. Ezzel csinálják a VS doksiját is. Van még kétely valakiben?
Az is jó, hogy nem kell kézzel írnom a konfigját, mert van hozzá Sandcastle Help File Builder, ami hasonló GUI, mint az ndochoz volt.
Aztán, hogy ne kelljen sokat gépelni az xml kommenteket, ott a GhostDoc. Beépül a VS-be, és CTRL-ALT-D-vel létrehoz egy komment vázat az adott kódrészhez. Meglepően ügyesen, ha angol neveket használunk a kódban.
Mindhárom eszköz zseniális és INGYENES. Aki ezek után nem dokumentálja a kódját, annak 1-es. :)

C# XML kommentek

Wednesday, September 30th, 2009

Ha valaki szeretne rászánni 2 órát, hogy alaposan megértse, íme egy jó doksi hozzá.

Unraveling the Mysteries of .NET 2.0 Configuration

Monday, September 28th, 2009

Számomra rém unalmas téma, de ettől függetlenül nagyon fontos. Mivel elég gyéren dokumentálta le az ms, az itteni cikkek életmentőek lehetnek, akinek ezzel kell foglalkozni.

INVARIANT vs. ORDINAL újra

Wednesday, September 23rd, 2009

A korábban írt cikkhez kapcsolódó anyag, ebből már jobban átjön a különbség:
New Recommendations for Using Strings in Microsoft .NET 2.0.

* DO: Use StringComparison.Ordinal or OrdinalIgnoreCase for comparisons as your safe default for culture-agnostic string matching.
* DO: Use StringComparison.Ordinal and OrdinalIgnoreCase comparisons for increased speed.
* DO: Use StringComparison.CurrentCulture-based string operations when displaying the output to the user.
* DO: Switch current use of string operations based on the invariant culture to use the non-linguistic StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase when the comparison is linguistically irrelevant (symbolic, for example).
* DO: Use ToUpperInvariant rather than ToLowerInvariant when normalizing strings for comparison.
* DON’T: Use overloads for string operations that don’t explicitly or implicitly specify the string comparison mechanism.
* DON’T: Use StringComparison.InvariantCulture-based string operations in most cases; one of the few exceptions would be persisting linguistically meaningful but culturally-agnostic data.

Ebből az jön le, hogy használd vagy az ordinalt, vagy a “kulturált” megoldást, de ha lehet ne az invariantot.

# Ordinal comparisons are string comparisons in which each byte of each string is compared without linguistic interpretation. This is essentially a C runtime strcmp. Thus, “windows” would not match “Windows.” Where the context dictates that strings should be matched exactly, or demands conservative matching policy, this comparison should be used. Additionally, ordinal comparisons are the fastest because they apply no linguistic rules when determining a result.

# Case insensitive ordinal comparisons are the next most conservative, and ignore most casing. Thus, “windows” would match “Windows.” When dealing with ASCII characters, this policy is equivalent to that of StringComparison.Ordinal, but with the usual ASCII casing ignored. Thus, any character in [A, Z] (\u0041-\u005A) matches the corresponding one in [a,z] (\u0061-\007A). Casing outside the ASCII range uses the invariant culture’s tables…

Oridinal, case sensitive bináris összehasonlítás, gyors, ok.
Oridinal, case insensitive ascii karakterek esetén egyenlőnek tekinti a kis-nagybetűket, más karakterek esetén átvált az invariantra. Praktikusan szerintem ez azt jelenti, hogy ordinal case insensitive == invariant case insensitive.
Update: Jogos az előbbi, mert: “Comparisons made using OrdinalIgnoreCase are behaviorally the composition of two calls: calling ToUpperInvariant on both string arguments, and doing an Ordinal comparison.”

Közelebbről:


using System;

class Program
{
    static void Main(string[] args)
    {
        string kisalma = "alma";
        string nagyalma = "Alma";
        Compare(kisalma, nagyalma);
        Console.WriteLine();
        kisalma = "őlma";
        nagyalma = "Őlma";
        Compare(kisalma, nagyalma);
    }

    private static void Compare(string a, string b)
    {
        Test(a, b, StringComparison.CurrentCulture);
        Test(a, b, StringComparison.CurrentCultureIgnoreCase);
        Test(a, b, StringComparison.InvariantCulture);
        Test(a, b, StringComparison.InvariantCultureIgnoreCase);
        Test(a, b, StringComparison.Ordinal);
        Test(a, b, StringComparison.OrdinalIgnoreCase);
    }

    private static void Test(string a, string b, StringComparison c)
    {
        int compRes = string.Compare(a, b, c);

        Console.WriteLine("{0}, {1} {2} {3}", c, a,
            compRes == 0 ? "==" : (compRes < 0 ? "<": ">"),
            b);
    }
}

CurrentCulture, alma < Alma
CurrentCultureIgnoreCase, alma == Alma
InvariantCulture, alma < Alma
InvariantCultureIgnoreCase, alma == Alma
Ordinal, alma > Alma
OrdinalIgnoreCase, alma == Alma

CurrentCulture, őlma < Őlma
CurrentCultureIgnoreCase, őlma == Őlma
InvariantCulture, őlma < Őlma
InvariantCultureIgnoreCase, őlma == Őlma
Ordinal, őlma > Őlma
OrdinalIgnoreCase, őlma == Őlma

Nem teljes a teszt, de látható, hogy az ordinal másként sorrendez, mint az invariant, nem nyelvi szempontok szerint, ezért is nem való GUI-hoz, de tudja, hogy a kis ő-nek a nagy Ő a párja. Az invariant se elég okos persze, mivel általános, nem ismeri egy adott nyelv szabályait, pl.


Thread.CurrentThread.CurrentCulture = new CultureInfo("hu-hu");
a = "cukor";
b = "csoki";
Compare(a, b);

CurrentCulture, cukor < csoki
CurrentCultureIgnoreCase, cukor < csoki
InvariantCulture, cukor > csoki
InvariantCultureIgnoreCase, cukor > csoki
Ordinal, cukor > csoki
OrdinalIgnoreCase, cukor > csoki

A cukor után van a csoki magyarban, mivel van csé betűnk. Az invariant értelemszerűen nem tud erről, ezért a csokit cé, es, okinak olvassa, és így is rendez.

Ezek után egyet kell értsek a cikkel, használjuk kulturált kultúrát nyelvi rendezéshez, fájl elérési utakhoz, ojjektum nevekhez, stb. oridinalt, az invariantot meg felejtsük el.
Legalábbis ma így látom. Comments welcome.