Spotkanie z TaskCompletionSource – Cz. 1 I promise I will call back

Programowanie asynchroniczne w C# stało całkiem znośne od kiedy język ten posiada słowa kluczowe async i await. Rozwiązanie to tak udało się tak dobrze, że zaczyna pojawiać się w innych językach. VB.Net podobno już je ma (może któryś z czytelników już go używał i mógłby podzielić się swoimi doświadczeniami?), architekci projektujący C++ i Javascript także nad usilnie pracują na wdrożeniem podobnych mechanizmów.

Czytaj dalej Spotkanie z TaskCompletionSource – Cz. 1 I promise I will call back

NCrunch – Jakie to, kurwa, dobre

Cytatem Youtubowego kucharza (Food Emperor) polecam wszystkim narzędzie wniesione do zespołu, w którym pracuję, przez nowego kolegę Sebastiana.

Wiem że o NCrunchu pisało już wielu. Jednak nikomu nie udało się mnie do niego przekonać, więc jest pewnie wielu innych sceptyków tego narzędzia i do nich właśnie jest ten artykuł.
Jest to narzędzie pełniące rolę sumienia dla osób stosujących TDD. Każda linijka kodu C#-owego jest oznaczona kropą: białą gdy brakuje testów ją pokrywajacych, czerwoną gdy jakiś test nie przechodzi i zieloną gdy wszystko jest OK.
Działanie NCruncha

Czytaj dalej NCrunch – Jakie to, kurwa, dobre

Gdy HtmlHelper to za mało

Czytając ostatnio o rozszerzeniach do klasy HtmlHelper, stwierdziłem że w wraz z rozwojem projektu ich różnorodność może stać się trudna do ogarnięcia i przydałoby się je pogrupować, zorganizować.
Zacząłem się więc zastanawiać jak takie rozwiązanie mogłoby wyglądać. Zaznaczam przy tym że sam tego jeszcze nie doświadczyłem, a koncepcje dalej przedstawione są raczej propozycjami niż wskazówkami.

Czytaj dalej Gdy HtmlHelper to za mało

Sqlite – agregacja bez group by

Krótki wpis o błędzie znalezionym przeze mnie w Sqlite.

Jeżeli w zapytaniu umieści się funkcję agregacyjną i zwykłą kolumnę, a w zapytaniu nie wstawi się group by to zapytanie wykona się bez zgłoszenia błędu. Wynikiem takiego zapytania będzie jeden wiersz w którym funkcja agregacyjna zwróci poprawną wartość natomiast w kolumnie wyciągniętej ze "zwykłej" kolumny znajdzie się jakaś  wartość (prawdopodobnie pierwsza z brzegu). W SQL Serverze po prostu się wywala w innych silnikach nie sprawdzałem.

Amatorskie spojrzenie na EmguCV

EmguCV jest .Netowym wrapperem do OpenCV – potężnej biblioteki do przetwarzania i analizy obrazów (zarówno statycznych jak i ruchomych) oraz innych sygnałów cyfrowych.

Ważną cechą biblioteki jest fakt iż stara się ona wykorzystać jak najlepiej zasoby dostępne na maszynie na której jest ona uruchomiona, ze szczególnym uwzględnieniem karty grafiki.

Oprócz tego OpenCV wspiera machine learning, jednak tej części biblioteki jeszcze nie dotykałem.

Co ma wspólnego programista .Net zajmujący się nudnymi dokumentami korporacyjnymi z przetwarzaniem obrazów? Niewiele. Po prostu od czasu do czasu trzeba spróbować czegoś nowego for-fun więc znalazłem pierwszy lepszy pretekst żeby pobawić się się EmguCV.

Od razu zaznaczę, że nie czytałem o żadnych dobrych praktykach, ani nawet nie zagłębiałem się w tutoriale tylko postanowiłem rozpocząć naukę przez eksplorację wspartą przez google i stackoverflow. W dodatku skoncentrowałem się tylko na podstawowych przekształceniach obrazów statycznych.

1. Instalacja

Instalację należy zacząć od wejścia na stronę http://sourceforge.net/projects/emgucv/ i pobrania instalatora i klikania next, next, next. Już sam rozmiar paczki instalacyjnej robi wrażenie (200+MB), Natomiast po jej zainstalowaniu stracimy prawie 2GB dysku. Liczbę tą można zmniejszyć rezygnując z instalacji przykładowych solucji.

2. Pierwsze podpięcie

Podłączenie Emgu do solucji w celu przekształcania obrazów statycznych jest opisane na stronie http://www.emgu.com/wiki/index.php/Setting_up_EMGU_C_Sharp zgodnie z tym tutorialem należy do swojego projektu dodać referencje do dll’ek EmguCV.dll, EmguCV.UI.dll oraz EmguCV.Util.dll biblioteki te znajdują się w katalogu “<ścieżka_do_emgu>\bin”.

Biblioteki te nie są jednak samodzielnymi bibliotekami tylko wrapperami do bibliotek napisanych w c++ które posiadają wersje 32 i 64 bitową co za tym idzie nasza aplikacja albo będzie miała 2 wersje albo nie będzie działać na niektórych stacjach roboczych.

Powoduje to że powinniśmy ustawić właściwość “Target platform” na “x64” (Properties w naszym csproj, zakładka Build).

Drugim problemem o którym wspomniany wcześniej tutorial nie mówi to fakt iż zależności między bibliotekami się trochę pozmieniały i dodanie do projektu tylko i wyłącznie opencv_core<ver>.dll i opencv_imgproc<ver>.dll (znajdujacych się w katalogu  “<ścieżka_do_emgu>\bin\x64”) sprawi że po wykorzystaniu jakiejkolwiek metody z biblioteki EmguCV dostaniemy w twarz błędem o mało mówiącym komunikacie “'System.TypeInitializationException' occurred in Emgu.CV.dll”. Grzebiąc po internecie znalazłem poradę aby po prostu przekopiować cały katalog “<ścieżka_do_emgu>\bin\x64” do swojej solucji tak aby lądował w katalogu bin\Debug wadą tego rozwiązania jest jednak fakt iż cały katalog waży 566MB. Jeżeli ktoś zna minimalny podzbiór dll’ek potrzebny do otworzenia obrazu z pliku i wyświetlenia go w Picturebox będę wdzięczny.

3. Pokaż cy…kod

Podstawową klasą z której korzystałem podczas moich zabaw jest Image<TColor, TDepth> jak widać jest to klasa generyczna pierwszy parametr oznacza w jaki sposób można operować na kolorach obrazu. Przykładami typów które można tam postawić są Bgr, Gray, Bgr556, Hsv i kilka innych. Parametr TDepth oznacza typ używany do określenia głębi kolorów (osobiście zawsze tam wrzucam byte).

Klasa ta posiada kilka kontruktorów z których według mnie najważniejsze to:

public Image(Bitmap bmp);

Służy do konwesji bitmapy z przestrzeni nazw System.Drawing na obraz który nadaje się do przetworzenia przez Emgu.

public Image(string fileName)

Pozwala otworzyć obraz z pliku, metoda nie wspiera formatów Uri, jednak wnioskując po treści zgłaszanego wyjątku obsługa taka zostanie dodana

public Image(int width, int height, TColor value);

Tworzy obraz o zadanych wymiarach i kolorze

Niestety pośród konstruktorów brakuje otwarcia obrazu z dowolnego strumienia, jednak można się posiłkować następującym obejściem:

using (Stream stream = new SomeStream())
            {
                using (var memoryStream = new MemoryStream())
                {
                    stream.CopyTo(memoryStream);
                    memoryStream.Position = 0;

                    Image<Bgr, byte> image = Image<Bgr, byte>.FromRawImageData(memoryStream.ToArray());
                }
            }

Klasa Image zawiera szereg metod które można podzielić na dwie grupy:

  • zaczynające się od podkreślenia – zmieniają obiekt obrazu na którym są wywoływane

  • nazywające się jak pan Bóg przykazał – generują kopię obrazu z naniesionymi zmianami

Część metod posiada wersję z podkreśleniem i bez. Intuicja mi podpowiada że metody “podkreślnikowe” są wydajniejsze ale nie są nie można ich używać bezpiecznie w środowisku wielowątkowym (nie szukałem jednak potwierdzenia tej tezy w dokumentacji).

Pierwszą metodą w klasie Image, którą postanowiłem przybliżyć jest Convert<TColor, TDepth>  metoda da pozwala na stworzenie obrazu opisanego w inny sposób lub mającego inną głębię.

// Dzięki temu możemy operować na głębi i nasyceniu kolor&oacute;w
var image2 = image.Convert&lt;Hsv, byte&gt;();
// Konwersja na skalę szarości - nieodwracalna
var image3 = image.Convert&lt;Gray, byte&gt;();

Drugą ważną dla mnie metodą jest .ToBitmap pozwalająca konwertować obraz do klasy System.Drawing.Bitmap co pozwala wyświetlić wynik przekształceń w kontrolkach Windows Forms.

Eclipse Database Development Incorrect syntax w poprawnym skrypcie

Eclipse Database Development Tools to całkiem fajne narzędzie do współpracy z SQL-owymi bazami danych. Posiada kolorwanie składni, możliwość edycji danych w tabelkach, jest zintegrowane z Eclipsem, działa z każdym silnikiem SQL z jakiego kiedykolwiek korzystałem, a co najważniejsze jest dostępne za free.

Jednak podczas przenoszenia bloga na nowy hosting i domenę przytrafiła mi się przykre doświadczenie. Próbowałem z tego narzędzia odpalić skrypt migrujący dane. I zamiast pełnej bazy zobaczyłem komunikat o niepoprawnej składni.

Okazuje się że przy odpaleniu dłuższych skryptów narzędzie to najpierw robi paskudne dzielenie skryptu po średnikach nie patrząc gdzie one się znajdują a następnie uruchamia skrypt po skrypcie.

Ponieważ mój Blog jest programistyczny napisy zawierały spore ilości średników. Skończyło się na zassaniu skryptu przez konsolę. 

Lazy Yield Problems

Zanim przejdę do sedna sprawy nakreślę najpierw ciąg wydarzeń który mnie ku napisaniu tego posta skłonił.

Ostatnimi czasy wykonywałem małą biblioteczkę "na własne potrzeby" której podstawą był interfejs który można przedstawić następująco:

public interface IProcessor&lt;T&gt;
{
	IEnumerable&lt;Result&gt; Process(T input);
}

Interfejs ten ma wiele implementacji jednak z punktu widzenia tego postu kluczowy jest "AggregateProcessor" który łączy wyniki kilku procesorów.

class AggregateProcessor&lt;T&gt; : IProcessor&lt;T&gt;
{
	private IProcessor&lt;T&gt;[] subProcessors;

	public AggregateProcessor(params IProcessor&lt;T&gt;[] subProcessors)
	{
		this.subProcessors = subProcessors;
	}

	public IEnumerable&lt;Result&gt; Process(T input)
	{
		return subProcessors.SelectMany(p =&gt; p.Process(input));

		/* ekwiwalent
		foreach (var processor in subProcessors)
		{
			foreach (var result in processor.Process(input))
			{
				yield return result;
			}
		}
		 */
	}
}

Następnie dla tej klasy napisałem prosty test sprawdzający czy podprocesor zostanie wywołany po wywołaniu Process na AggregateProcessorze.

Test jest napisany w NUnicie do Mockowania wykorzystałem FakeItEasy.

[Test]
public void CheckIfSubProcessorIsCalled()
{
	// Given
	var parameter = new TestClass();

	var fakeProcessor = A.Fake&lt;IProcessor&lt;TestClass&gt;&gt;();
	A.CallTo(() =&gt; fakeProcessor.Process(parameter)).Returns(Enumerable.Empty&lt;Result&gt;());

	var aggregate = new AggregateProcessor&lt;TestClass&gt;(fakeProcessor);

	// When
	aggregate.Process(parameter);

	// Then
	A.CallTo(() =&gt; fakeProcessor.Process(parameter)).MustHaveHappened();
}

Wielkie było moje zdziwienie gdy okazało się że Test nie przechodzi.

Przyczyną tej sytuacji jest fakt iż zarówno operacje w zapytaniu LINQ jak i operacja yield wywoływane są w momencie iteracji po wyniku metody której w tym przykładzie nie ma. W większości przypadków fakt ten nie ma większego znaczenia ponieważ iteracja zazwyczaj w końcu następuje, chyba że tak jak w tym przypadku metody będącej funkcją używamy jak metody będącej procedurą, albo wykonujemy na wyniku operacje typu Any, First/FirstOrDefault które nie iterują po całym wyniku.

Wracając do mojego przypadku ostatecznie stwierdziłem że fakt wywołania metody wewnętrznej nie jest dla mnie istotny, a bardziej obchodzi mnie zwrócenie wyniku procesora wewnętrznego.

[Test]
public void CheckIfSubProcessorIsCalled()
{
	// Given
	var parameter = new TestClass();
	var result = new Result();

	var fakeProcessor = A.Fake&lt;IProcessor&lt;TestClass&gt;&gt;();
	A.CallTo(() =&gt; fakeProcessor.Process(parameter)).Returns(new[] { result });

	var aggregate = new AggregateProcessor&lt;TestClass&gt;(fakeProcessor);

	// When
	var resultCollection = aggregate.Process(parameter);

	// Then
	CollectionAssert.Contains(resultCollection, result);
}

Drugim zagrożeniem jakie wiąże się z yield i LINQ jest fakt iż wszystkie operacje wykonywane pod spodem wykonują się przy każdej iteracji po wynikowej kolekcji. Sam się na tym nigdy nie przejechałem ponieważ przed błędem tego typu skutecznie chroni mnie Resharper, jednak ludzi nie posiadających tego genialnego narzędzia warto ostrzec że poniższy kod:

static void Main(string[] args)
{
	var sourceCollection = Enumerable.Range(1, 10);

	var resultCollection = from number in sourceCollection
						   select Compute(number);

	foreach (var number in resultCollection)
	{
		Console.Write(&quot;{0}, &quot;, number);
	}

	Console.WriteLine();

	foreach (var number in resultCollection)
	{
		Console.Write(&quot;{0}, &quot;, number);
	}
}

private static int Compute(int parameter)
{
	// Some Heavy operation
	Thread.Sleep(1000);
	return parameter + 1;
}

będzie uruchamiał się 20 a nie 10 sekund. Jakimś obejściem tego problemu jest użycie metody ToList. Najlepiej w następujący sposób:

static void Main(string[] args)
{
	var sourceCollection = Enumerable.Range(1, 10);

	var resultCollection = from number in sourceCollection
						   select Compute(number);

	/*
	 * Przed wywołaniem ToList upewniam się że kolekcja nie jest już listą
	 * W tym przypadku nie ma to sensu ponieważ wiem że kolekcja nie jest listą
	 */
	var resultList = resultCollection as List&lt;int&gt; ?? resultCollection.ToList();

	foreach (var number in resultList)
	{
		Console.Write(&quot;{0}, &quot;, number);
	}

	Console.WriteLine();

	foreach (var number in resultList)
	{
		Console.Write(&quot;{0}, &quot;, number);
	}
}

Podsumowując zarówno LINQ jak i yield są bardzo fajnymi narzędziami, przed ich użyciem należy jednak przynajmniej w minimalnym stopniu dowiedzieć się jak one działają.

Świąteczne małpki

W ostatnim czasie w pracy spotkałem się z problemem który w uproszczeniu wygląda następująco:

Mamy listę obiektów typu Dział zawierających kolekcje obiektów typu Wydział zawierających kolekcję obiektów typu pracownik. Problemem do rozwiązania jest policzenie średniej Wieku wszystkich pracowników. Zakładamy dodatkowo że baza nie jest tuż za płotem tylko znajduje się za WebServicem do którego dokładanie metod jest z jakiś przyczyn problematyczne, więc nie możemy po prostu złożyć składnego selecta.

Problem wydaje się być banalny wystarczy zmontować coś takiego:

int ilosc = 0;
            int suma = 0;

            foreach (Dzial dzial in dzialy)
            {
                foreach (Wydzial wydzial in dzial.Wydzialy)
                {
                    foreach (Pracownik pracownik in wydzial.Pracownicy)
                    {
                        ilosc++;
                        suma += pracownik.Wiek;
                    }
                }
            }

            double wynik = ((float)suma) / ilosc;

Jest to jednak kod mało czytelny. A jeżeli operacja byłaby bardziej złożona niż średnia robi się jeszcze mniej ciekawie.

Rozwiązaniem jakie narzuca się bardziej doświadczonym programistom .Net jest użycie LINQ. Rozwiązanie to ma jednak wadę: nie chodzi na kolekcjach kolekcji tylko na jednowarstwowych kolekcjach. Można ten problem rozwiązać przez wyliczenie sobie pomocniczej kolekcji i na niej wykonania działania:

           List&lt;Pracownik&gt; pracownicy = new List&lt;Pracownik&gt;();

            foreach (Dzial dzial in dzialy)
            {
                foreach (Wydzial wydzial in dzial.Wydzialy)
                {
                    foreach (Pracownik pracownik in wydzial.Pracownicy)
                    {
                        pracownicy.Add(pracownik);
                    }
                }
            }

            double wynik = pracownicy.Average(p =&gt; p.Wiek);

Jest to rozwiązanie dobre jednak tworzenie i napełnianie listy w przypadku dużych kolekcji może okazać się zasobożerne.

Dlatego też postanowiłem zrobić świąteczny prezent sobie i innym programistom i stworzyć małpkę która za nas będzie biegała po drzewach i strukturach drzewo-podobnych. W ten sposób powstało rozszerzenie ExtractSubCollection biblioteki Szogun1987.Monkey. Po wykorzystaniu tej metody powyższy kod zostaje skrócony do 4 w miarę czytelnych linijek:

double result = 
                dzialy.ExtractSubCollection(d =&gt; d.Wydzialy)
                .ExtractSubCollection(w =&gt; w.Pracownicy)
                .Average(p =&gt; p.Wiek);

W bibliotece znajdują się dodatkowo rozszerzenia GetAllSubNodes, GetLeafs i GetRoot ułatwiające przeglądanie zwykłych (tj. składających się z węzłów spójnych pod względem typu) Drzew.

Najprostszą z nich jest GetRoot która jako parametr pobiera obiekt – węzeł drzewa i Funkcję zwracającą element nadrzędny węzła. Jako korzeń uznawany jest element dla którego powyższa funkcja zwróci null.

Kolejną metodą jest GetAllSubNodes pozwala ona na przeiterowanie po wszystkich węzłach znajdujących się poniżej węzła na którym wywoływane jest rozszerzenie (łącznie z nim samym). Jako drugi parametr przyjmowana jest funkcja zwracająca kolekcję elementów podrzędnych.

Ostatnią metodą w bibliotece jest GetLeafs pozwalająca przeiterować się po liściach drzewa z niego się wywodzących.

Dodatkowo w bibliotece znajduje się interfejs ITreeNode którego zaimplementowanie sprawia że do funkcji GetAllSubNodes, GetLeafs i GetRoot nie trzeba przekazywać funkcji wyciągających "najbliższych członków rodziny" danego elementu.

Dołożyłem starań aby biblioteka była napisana w sposób powodujący jak najniższe zużycie zasobów systemowych (pamięci i procesora). Podobnie jak w LINQ wszelkie funkcje i metody przekazywane do moich rozszerzeń są wywoływane w momencie iteracji.

Na koniec kwestie prawne, (Co by było wszystko na papierze znaczy się monitorze):

Zezwalam na nieodpłatne korzystanie ze skompilowanej wersji biblioteki. Biorę na siebie poprawki ewentualnie wykrytych błędów, przy czym nie naprawiam błędów wynikających ze współbieżnego dostępu do drzewa. Nie gwarantuję także wprowadzania ulepszeń.

Kodu źródłowego nie udostępnię w tym roku. Zakładam taką możliwość w pierwszym kwartale roku przyszłego (2012).

Biblioteki są do pobrania Tutaj

Problem zwrotnych referencji w WCF

Podczas wielu batalii z WCF'em natknąłem się na konieczność umieszczenia w obiekcie będącej elementem jakiejś kolekcji referencji do obiektu posiadającego tą kolekcję. (Przyznam że konieczność ta była związana z faktem iż w przesyłanej klasie była zaszyta część logiki biznesowej a tak się nie powinno robić ale jak mus to mus). Tak więc posiadałem DataContract'y podobne do tych:

    [DataContract]
    public class Parent
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public List&lt;Children&gt; Childrens { get; set; }
    }

    [DataContract]
    public class Children
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public Parent Parent { get; set; }
    }

Próba pobrania obiektu Parent zakończyła się błędem:

WCFBlad1.png

Znalezienie przyczyny błędu było na tyle trudniejsze że pomiędzy buildem a napisaniem DataContractu robiłem całą masę innych rzeczy.

Błąd ten jest spowodowany wyjątkiem StackOverflow po stronie serwera, wynika on z faktu że przy próbie serializacji klasy Children mechanizm docierając do pola Parent nie orientuje się że już ten obiekt serializował albo właśnie go serializuje.

Rozwiązaniem tego problemu jakie znalazłem googlając było ustawienie właściwości IsReference w klasie rodzica i rzeczywiście w tak prostym przypadku rozwiązanie to działa jak należy.

    [DataContract(IsReference=true)]
    public class Children
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public Parent Parent { get; set; }
    }

Moja sytuacja była jednak trochę bardziej złożona. Po pierwsze zarówno Parent jak i Children dziedziczyło po jednym typie bazowym:

    [KnownType(typeof(ChildrenBP))]
    [KnownType(typeof(ParentBP))]
    [DataContract]
    public class SuperBP
    {
        [DataMember]
        public string Name { get; set; }
    }
    
    [DataContract(IsReference=true)]
    public class ParentBP : SuperBP
    {
        [DataMember]
        public List&lt;ChildrenBP&gt; Childrens { get; set; }
    }
	
	[DataContract]
    public class ChildrenBP : SuperBP
    {
        [DataMember]
        public ParentBP Parent { get; set; }
    }

Ustawienie znacznika IsReference na klasie ParentBP i pobranie referencji kończy się błędem:

pictures/WCFBlad2.png

W większości przypadków należy postąpić zgodnie z komunikatem błędu i ustawić IsReference na klasie bazowej. Jeżeli jednak z jakiś powodów nie chcemy tego robić możemy usunąć znacznik DataMember z pola Parent w klasie ChildrenBP i posiłkować się znacznikiem OnDeSerialized.

    [KnownType(typeof(ChildrenBP))]
    [KnownType(typeof(ParentBP))]
    [DataContract]
    public class SuperBP
    {
        [DataMember]
        public string Name { get; set; }
    }
	
    [DataContract]
    public class ParentBP : SuperBP
    {
        [OnDeserialized]
        public void OnDeserialized(StreamingContext context)
        {
            foreach (ChildrenBP child in Childrens)
            {
                child.Parent = this;
            }
        }

        [DataMember]
        public List&lt;ChildrenBP&gt; Childrens { get; set; }
    }
	[DataContract]
    public class ChildrenBP : SuperBP
    {

        public ParentBP Parent { get; set; }
    }

Słów kilka o krotkach.

Chciałem dzisiaj opisać trochę moje doświadczenia z krotkami.

Pomimo że ich konstrukcja nie jest złożona, a istnienie klasy o ich cechać było wyczekiwane dość długo to pojawiły się one w .Net dopiero w wersji 4.0, a ich pojawienie się zostało zdecydowanie zagłuszone przez inne nowinki w tej wersji.

Pierwszym narzucającym się zastosowaniem dla krotek jest wielotypowana tablica. Tablica taka przydaje się gdy musimy przerzucić listę kilkuelementowych paczek obiektów pomiędzy dwoma miejscami w kodzie. Przykładem takiego miejsca wywoływanie operacji na bazie dla każdego rekordu zwróconego przez obiekt implementujące IDataReader. Jak wiadomo przy próbie wywołania zapytania przed zamknięciem Readera następuje zgłoszenie wyjątku. Problem taki rozwiązywało się na kilka sposobów (oczywiście nie polegających na rezygnowaniu z Readera):

1) Utworzenie kilku list dla każdego przepisywanego pola – powoduje konieczność stosowania pętli for zamiast foreach

2) Utworzenie listy tablic obiektów (listy słowników, lub innej wariacji generycznej) – fajne rozwiązanie dopuki nie potrzebna nam informacja o rzeczywistym typie elementu (wtedy trzeba rzutować).

3) Utworzenie klasy i przesyłanie listy obiektów tej klasy – C# to nie Java tutaj duża lisczba klas kuje w oczy ;]

4) Klasy anonimowe – byłyby idealne gdyby nie problem z nacechowaniem ich typem listy

5) Stosowanie elementu klasy stworzonej w innym celu (np. KeyValuePair<T1, T2>)

Zastosowanie krotek jest rozwiązaniem łączącym znaczną część zalet powyższych rozwiązań jednocześnie nie posiada większości ich wad:

List&lt;Tuple&lt;int, string&gt;&gt; tempList = new List&lt;Tuple&lt;int, string&gt;&gt;();
using(SqlCommand cmd = new SqlCommand())
{
	cmd.Connection = con;
	cmd.CommandText = &quot;select id, nazwa from Tabela1&quot;;
	using(IDataReader reader = cmd.ExecuteReader())
	{
		while(reader.Read())
		{
			tempList.Add(new Tuple&lt;int, string&gt;());
		}
	}
}
using(SqlCommand cmd = new SqlCommand())
{
	cmd.Connection = con;
	cmd.CommandText = &quot;insert into Tabela2(id, nazwa) values (@id, @nazwa)&quot;;
	foreach(var tuple in tempList)
	{
		cmd.AddWithValue(&quot;@id&quot;, tuple.Item1);
		cmd.AddWithValue(&quot;@nazwa&quot;, tuple.Item2);
		cmd.ExecuteNonQuery();
	}
}

Inną ciekawostką związaną z krotkami jest fakt iż mają solidnie zaprogramowane funkcje GetHashCode i Equals, przez co można je wykorzystać żeby ułatwić sobie implementację tych metod:

class ExampleClass
{
	public int IdPart1 { get; set; }
	public int IdPart2 { get; set; }

	public override int GetHashCode()
	{
		return new Tuple&lt;int, int&gt;(IdPart1, IdPart2).GetHashCode();
	}

	public override bool Equals(object obj)
	{
		if (obj is ExampleClass)
		{
			ExampleClass other = (ExampleClass)obj;
			return new Tuple&lt;int, int&gt;(IdPart1, IdPart2).Equals(new Tuple&lt;int, int&gt;(other.IdPart1, other.IdPart2));
		}
		return false;
	}
}

albo z wykorzystaniem LambdaEqualityComparer

class ExampleClass
{
	public int IdPart1 { get; set; }
	public int IdPart2 { get; set; }

	private LambdaEqualityComparer&lt;ExampleClass, Tuple&lt;int, int&gt;&gt; comparer = 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;new LambdaEqualityComparer&lt;ExampleClass, Tuple&lt;int, int&gt;&gt;(ec =&gt; new Tuple&lt;int, int&gt;(ec.IdPart1, ec.IdPart2));

	public override int GetHashCode()
	{
		return comparer.GetHashCode(this);
	}

	public override bool Equals(object obj)
	{
		return comparer.Equals(this, obj as ExampleClass);
	}
}

Krotki implementują też interfejs IComparable co może być przydatne jeżeli chcielibyśmy zaimplementować ten interfejs w swoich klasach (prawie klasyczny przykład Dekoratora).

Kolejnymi interfejsami implementowanymi przez Krotki są IStructuralEquatable i IStructuralComparable jest to związane z silnym wykorzystywaniem ich w języku F# (niestety jeszcze do niego nie przysiadłem). Więcej informacji na ten temat można uzyskać od Marcina Najdera z firmy Comarch który silnie promuje ten język na konferencjach.

Ze względu na fakt iż krotki po zainicjowaniu nie zmieniają swojego stanu mają podobno zastosowanie w aplikacjach współbieżnych (podobno bo sam nie miałem okazji się o tym przekonać).

Na koniec chciałbym dodać kilka słów o wykrytych przeze mnie niedopracowaniach krotek.

Pierwsza niedogodność Nazwy Item1, Item2, Item3… często uniemożliwiają domyślenie się co jest co, jednak po pierwsze nie mam innego pomysłu na nazwy tych właściwości, po drugie z założenia powinny być one wykorzystywane jako tymczasowe pojemniki na obiekty.

Druga fakt iż nie da się ich łatwo zserializować i deserializować (co też nie jest ich poprawnym zastosowaniem).

W komentarzach proszę o umieszczanie pomysłów na inne zastosowanie dla krotek.