poniedziałek, 28 kwietnia 2014

[C#|Visual Studio] C#: Reflection

Reflection to potężne narzędzie, za pomocą którego można zrobić wiele dobrego jak również i wiele złego w każdej .NET-owej aplikacji. Najważniejsze klasy, to Type, Assembly, Activator oraz wszystkie opisujące składowe typów, czyli MethodInfo, PropertyInfo, ConstructorInfo, FieldInfo, EventInfo itd. Poza swobodnym przeglądaniem assemblies za pomocą kodu, reflection ma zastosowanie w operacjach wykonywanych dynamicznie na starcie aplikacji. W każdym innym wypadku musimy liczyć się ze sporym spadkiem wydajności w stosunku do kodu statycznie typowanego.

Klasa assembly pozwala na odczytywanie metadanych z różnych assemblies a także ładowanie ich.

var current = Assembly.GetExecutingAssembly();

Console.WriteLine("Current assembly: " + current.FullName);

var fromGac =
    Assembly.Load("System.Xml.Linq,Version=4.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089");

var restSharpAssembly = Assembly.LoadFrom("RestSharp.dll");

Console.WriteLine("\nLoaded assembly: " + restSharpAssembly.FullName);
var dependant = restSharpAssembly.GetReferencedAssemblies();

Console.WriteLine("\nReferenced assemblies:");
foreach (var assemblyName in dependant)
    Console.WriteLine("\t" + assemblyName.FullName);

Metoda GetExecutingAssembly zwróci nam informacje o assembly, w którym ją wywołujemy (np. plik exe). Za pomocą metody Load możemy ładować assemblies z Global Assembly Cache, podając full name. Z kolei dla assemblies bez strong name możemy użyć metody LoadFrom lub LoadFile szukających na dysku. Jeżeli interesują nas zależne assemblies, możemy uzyskać do nich dostęp przy użyciu metody GetReferencedAssemblies.

Mając obiekt typu Assembly możemy pobrać wszystkie publiczne typy w nim zdefiniowane - służy do tego metoda GetExportedTypes..

Console.WriteLine("\nPublic types:");
            var types = restSharpAssembly.GetExportedTypes();

            foreach (Type type in types)
                Console.WriteLine("\t" + type);

            var stype = (from type in types
                        orderby type.GetCustomAttributes(true).Count() descending
                        select type).First();

            var instance = Activator.CreateInstance(stype);
            Console.WriteLine("\nCreated type instance:" + stype.FullName);

            var props = stype.GetProperties();
            Console.WriteLine("Properties:");
            foreach (var propertyInfo in props)
                Console.WriteLine("\t" + propertyInfo.Name);

            var tfields = (from type in types
                           let fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
                           orderby fields.Count() descending
                           select fields).First();

            foreach (var fieldInfo in tfields)
                Console.WriteLine("\t" + fieldInfo.Name);

Na każdym z typów możemy sprawdzić, jakie zdefiniowano atrybuty (GetCustomAttributes), a także pobrać wszystkie properties (GetProperties) czy pola (GetFields), także te prywatne, ustawiając odpowiednie flagi. Klasa Activator pozwala nam na tworzenie instancji dowolnego typu (rzutowanej do object). Mając taką instancję możemy pobrać MethodInfo i wywołać taką metodę przekazując do niej parametry.

var sw = new Stopwatch();
sw.Start();
var jsonArray = new JsonArray();
for (int i = 0; i < 1000000; i++)
{
    jsonArray.Add(i);
}
Console.WriteLine("Invoking instance method: {0} ms", sw.ElapsedMilliseconds);
sw.Restart();

var methodInfo = stype.GetMethod("Add");
for (int i = 0; i < 1000000; i++)
{
    methodInfo.Invoke(instance, new object[] {i});
}
Console.WriteLine("Invoking method via reflection: {0} ms", sw.ElapsedMilliseconds);
sw.Reset();

Porównanie takie wypada zdecydowanie na niekorzyść Reflection, głównie przez iterowanie po metadanych i boxing parametrów wejściowych.

Invoking instance method: 83 ms
Invoking method via reflection: 2241 ms

sobota, 26 kwietnia 2014

[C#|Visual Studio] C#: Cryptography

Podstawowe trzy obszary zastosowań kryptografii, którymi są: wyliczanie haszy, kryptografia symetryczna oraz kryptografia asymetryczna zostały w bardzo dobry sposób zaimplementowane w dot Necie w namespace System.Security.Cryptography. Podstawowe pojęcia związane z kryptografią to: plaintext - wiadomość do zaszyfrowania, szyfrowanie - proces obfuskacji danych, deszyfracja - proces odtwarzania oryginalnych danych oraz ciphertext - dane w postaci zaszyfrowanej. Różne techniki kryprograficzne zapewniają:
  • poufność - dane nie mogą być przeczytane przez nieodpowiednie osoby
  • integralność - możliwość weryfikacji, czy dane były modyfikowane
  • uwierzytelnianie - potwierdzenie tożsamości użytkownika
  • niezaprzeczalność - autor wiadomości nie może się jej wyprzeć

Hashing:

Funkcje haszujące konwertują wejście o zmiennej długości w wyjściowy ciąg bajtów o stałej długości tworząc skrót (nazywany także hashem) z wiadomości. Operacja taka jest nieodwracalna i charakteryzuje się tym, że małe zmiany ciągu wejściowego powodują duże zmiany w ciągu wyjściowym. Haszowanie stosuje się w celu zapewnienia integralności. W .NET mamy dostępne 6 klas dziedziczących po abstrakcyjnej HashAlgorithm. Wybór powinien być kompromisem pomiędzy długością klucza (ataki typu bruteforce), a szybkością wykonywania.

Console.WriteLine("Please enter secret message:");
var msg = Console.ReadLine();
var msgBytes = Encoding.UTF8.GetBytes(msg);

HashAlgorithm[] algorithms = { 
                                 new MD5Cng(),
                                 new MD5CryptoServiceProvider(),
                                 new SHA1Managed(), 
                                 new SHA256Managed(),
                                 new SHA384Managed(),
                                 new SHA512Managed(),
                                 new RIPEMD160Managed()
                             };

var watch = new Stopwatch();
foreach (var hashAlgorithm in algorithms)
{
    watch.Start();
    byte[] hash = null;
    for (int i = 0; i < 100000; i++)
    {
        hash = hashAlgorithm.ComputeHash(msgBytes);
    }
    watch.Stop();
    Console.WriteLine("{0} ({1} bit): {2} ms ", 
        hashAlgorithm.GetType().Name, 8*hash.Length, watch.ElapsedMilliseconds);
    watch.Reset();
    Console.WriteLine(BitConverter.ToString(hash).Replace("-", ""));
}

Uzyskane wyniki:

Please enter secret message:
Hello world!

MD5Cng (128 bit): 933 ms
86FB269D190D2C85F6E0468CECA42A20

MD5CryptoServiceProvider (128 bit): 812 ms
86FB269D190D2C85F6E0468CECA42A20

SHA1Managed (160 bit): 257 ms
D3486AE9136E7856BC42212385EA797094475802

SHA256Managed (256 bit): 375 ms
C0535E4BE2B79FFD93291305436BF889314E4A3FAEC05ECFFCBB7DF31AD9E51A

SHA384Managed (384 bit): 2696 ms
86255FA2C36E4B30969EAE17DC34C772CBEBDFC58B58403900BE87614EB1A34B8780263F255EB5E65CA9BBB8641CCCFE

SHA512Managed (512 bit): 3323 ms
F6CDE2A0F819314CDDE55FC227D8D7DAE3D28CC556222A0A8AD66D91CCAD4AAD6094F517A2182360C9AACF6A3DC323162CB6FD8CDFFEDB0FE038F55E85FFB5B6

RIPEMD160Managed (160 bit): 423 ms
7F772647D88750ADD82D8E1A7A3E5C0902A346A3

Symmetric Algorithms:

Algorytmy symetryczne, to takie, w których do procesu szyfrowania i deszyfrowania używany jest ten sam klucz. Klucz taki musi być przechowywany w odpowiednio bezpiecznych warunkach i mieć odpowiednią długość, żeby uniknąć ataków typu brute-force. Obecnie powszechnie używanym algorytmem jest AES (Rijndael z min.128 bitowymi kluczami). Im dłuższy klucz, tym dłuższe szyfrowanie. Jeżeli chcemy wygenerować sobie klucz, najlepiej skorzystać z klasy RNGCryptoServiceProvider zapewniającej dużo lepszą "losowość", niż pozostałe narzędzia do generacji liczb pseudolosowych. Algorytm Rijndael często używany jest w trybie CBC, gdzie wynik kolejnej rundy szyfrowania zależy od rundy poprzedniej. Stąd obok klucza musimy znać wektor wejściowy (przed pierwszą rundą). Wektor taki - wektor IV (Initialization Vector) podaje się obok klucza przy dystrybucji. Konstruktor klasy RijndaelManaged wygeneruje taki wektor.

private static void Symmetric(byte[] msgBytes)
{
    var key = new byte[256/8];
    RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
    provider.GetBytes(key);

    var cipher = CreateCipher();

    cipher.Key = key;
    var encryptor = cipher.CreateEncryptor();
    var result = encryptor.TransformFinalBlock(msgBytes, 0, msgBytes.Length);

    Console.WriteLine("Secret key: " + BytesToString(key));
    Console.WriteLine("IV: " + BytesToString(cipher.IV));
    Console.WriteLine("Encrypted message: " + BytesToString(result));

    var cipher2 = CreateCipher();
    cipher2.Key = key;
    cipher2.IV = cipher.IV;

    var decryptor = cipher.CreateDecryptor();
    var originalMessage = decryptor.TransformFinalBlock(result, 0, result.Length);

    var s1 = BytesToString(originalMessage);
    var s2 = BytesToString(msgBytes);

    Console.WriteLine("Are messages equal ? " + s1.Equals(s2));
}

private static RijndaelManaged CreateCipher()
{
    RijndaelManaged cipher = new RijndaelManaged();
    cipher.KeySize = 256;
    cipher.BlockSize = 256;
    cipher.Padding = PaddingMode.ISO10126;
    cipher.Mode = CipherMode.CBC;
    return cipher;
}

Asymmetric algorithms:

Kryptografia asymetryczna nazywana także kryptografią klucza publicznego polega na tym, że mamy wygenerowaną parę kluczy: publiczny i prywatny. Klucze te uzupełniają się, to znaczy, że wiadomość zaszyfrowaną kluczem prywatnym można odszyfrować kluczem publicznym i na odwrót. Operacje takie są od 100 do 1000 razy wolniejsze od kryptografii symetrycznej, dlatego często algorytmów asymetrycznych używa się tylko do przesłania symetrycznego klucza sesji. Najpopularniejszy algorytm to RSA. Para kluczy przechowywana jest w kontenerze, przeważnie w systemie operacyjnym, ale można ją też eksportować do pliku XML. Wczytujemy ją z Xml metodę FromString lub podając nazwę kontenera systemowego przy użyciu klasy CspParameters.

CspParameters cp = new CspParameters();
cp.KeyContainerName = "SampleKeys";
cp.Flags = CspProviderFlags.UseMachineKeyStore;
RSACryptoServiceProvider cipher = new RSACryptoServiceProvider(cp);
var bytes = cipher.Encrypt(msgBytes, true);

Console.WriteLine("Encrypted message: " + BytesToString(bytes));
Console.WriteLine(cipher.ToXmlString(includePrivateParameters:true));

var s1 = BytesToString(msgBytes);
var s2 = BytesToString(cipher.Decrypt(bytes, true));

Console.WriteLine("Are messages equal ? " + s1.Equals(s2));

wtorek, 22 kwietnia 2014

[C#|Visual Studio] LINQ: join

Łączenie dwóch kolekcji w LINQ można wykonać na kilka sposobów:
  • inner join
  • group join
  • left outer join
Podstawowy i najczęściej używany sposób to inner join, czyli po prostu zwykłe złączenie kolekcji, przy czym jeżeli istnieją elementy, które nie mają swoich odpowiedników w drugiej kolekcji, to zostaną pominięte.

internal class Team
{
    public int Id { get; set; }
    public string Name { get; set; }
}

internal class Player
{
    public int Id { get; set; }
    public int TeamId { get; set; }
    public string Name { get; set; }
}

//...

var teams = new Team[3];
for (int i = 0; i < teams.Length; i++)
{
    teams[i] = new Team(){Id = i};
}

teams[0].Name = "Liverpool";
teams[1].Name = "Chelsea";
teams[2].Name = "Arsenal";

var players = new Player[3];
for (int i = 1; i <= players.Length; i++)
{
    players[i-1] = new Player() { Id = i - 1, TeamId =  i};

}

players[0].Name = "Hazard";
players[1].Name = "Ramsey";
players[2].Name = "Rooney";

var inner = from player in players
            join team in teams on player.TeamId equals team.Id
            select new
            {
                player.Name,
                TeamName = team.Name
            };

Console.WriteLine("Inner join:");
foreach (var result in inner)
{
    Console.WriteLine("{0}, {1}", result.Name, result.TeamName);
}    

Wypisane zostaną tylko dwa elementy, te, których indeksy TeamId pokrywają się z identyfikatorami drużyn (player[0], player[1])

Group join to konstrukcja sprowadzająca wynik do niepłaskiej struktury przypominającej wynik operacji group by. Operacja ta związana jest ze słowem kluczowym into, a jej wynik jest typu IEnumerable<IEnumerable<T>>. Możemy na tym oczywiście wywołać select spłaszczający wyniki.

var group = from team in teams
            join player in players on team.Id equals player.TeamId
                into playersWithTeams
            select new { Key = team.Name, Count = playersWithTeams.Count() };

foreach (var item in group)
{
    Console.WriteLine("{0}, {1}", item.Key, item.Count);
}

Left outer join przydaje się tam, gdzie z jakichś powodów chcemy dostać wszystkie elementy z kolekcji będącej lewą stroną operacji. Problemem jest, jak przedstawić elementy, które nie mają swojego odpowiednika w kolekcji, będącej prawą stroną join-a. W takim przypadku LINQ dostarcza extension metodę DefaultIfEmpty<T>, gdzie możemy podać, jak ma wyglądać taki "sztuczny" element.

var outer = from player in players
            join team in teams on player.TeamId equals team.Id
                into playersWithTeams
            from item in playersWithTeams.DefaultIfEmpty(new Team() {Name = "<Empty>"})
            select new
                       {
                           item.Name,
                           Player = player.Name
                       };

foreach (var item in outer)
{
    Console.WriteLine("{1}, {0}", item.Name, item.Player);
}            

Zostaną wypisane wszystkie trzy elementy kolekcji players.

poniedziałek, 21 kwietnia 2014

[C#|Visual Studio] C#: covariance, contravariance

Kiedy metoda korzystające z generycznych parametrów jest kompilowana przez JIT compiler, to CLR bierze jej IL i podmienia argumenty, a następnie tworzy kod specyficzny dla metody operującej na konkretnym typie. W związku z tym CLR wygeneruje osobny kod dla każdej kombinacji metod / parametrów, co nazywane jest code explosion. W związku z tym, konwersje pomiędzy typami o generycznych parametrach wydawać by się mogły technicznie trudne do realizacji. Wraz z C# 4.0 możemy konwertować w dwie strony za pomocą mechanizmów kowariancji i kontrawariancji.

Kowariancja
Typ generyczny z parametrem a może być rzutowany do innego typu z parametrem b jeżeli parametr b jest typem bazowym dla typu a. Słowo kluczowe C# dla kowariancji to out.

internal interface ICovariantable<out T> where T: new ()
{
    T GetValue();
}

public class MyCovariant<T> : ICovariantable<T> where T: new()
{
    public T GetValue()
    {
        return new T();
    }
}

public class SampleClass: object 
{
    public SampleClass()
    {
        
    }
}

Kontrawariancja
Typ generyczny z parametrem a może być rzutowany do innego typu z parametrem b jeżeli parametr a jest typem bazowym dla typu b. Słowo kluczowe C# dla kowariancji to in.

internal interface IContravariantable<in T>
{
    void SetValue(T param);
}

public class MyContravariant<T> : IContravariantable<T>
{
    public void SetValue(T param)
    {
        Console.WriteLine("value was set");
    }
}


static void Main(string[] args)
{
    //Covariance

    var instance1 = new MyCovariant<object>();
    var instance2 = new MyCovariant<SampleClass>();

    ICovariantable<object>[] instances = {instance1, instance2};

    //Contravariance

    var instance3 = new MyContravariant<object>();
    var instance4 = new MyContravariant<SampleClass>();

    IContravariantable<SampleClass>[] instances2 = { instance3, instance4 };

    Console.ReadKey();
}

Jeżeli nie wyspecyfikujemy parametru out dla pierwszego interfejsu, to kompilator zaprotestuje przeciwko umieszczeniu w tablicy drugiego elementu (instance2), natomiast jeżeli nie podamy in, to niedozwolone będzie umieszczenie w drugiej tablicy obiektu instance3.

Często wykorzystywane delegaty Func i Action wykorzystują kowariancję i kontrawariację: parametry wejściowe są typu in, natomiast wartości zwracane (w przypadku Func) są oznaczone jako out.