Pokazywanie postów oznaczonych etykietą generic. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą generic. Pokaż wszystkie posty

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.

sobota, 28 lipca 2012

[Wzorce projektowe] Service Locator

Analogia z życia:

Dzisiejsze telefony komórkowe oferuję wiele funkcjonalności. Możemy na przykład napisać wiadomość tekstową lub do kogoś zadzwonić. Z punktu widzenia użytkownika, wysłanie takiej wiadomości jest wykorzystaniem pewnej usługi. Piszemy wiadomość, podajemy numer i klikamy wyślij. To w jaki sposób wiadomość zostanie przekazana i ile za nią zapłacimy zależy między innymi od operatora sieci. Wkładając kartę SIM, możemy na starcie określić wspomniane warunki, aby później po uruchomieniu telefonu w każdej chwili korzystać z usługi w wybranej wersji.


Zastosowanie:

Wzorzec lokatora usług znajduje zastosowanie w sytuacji, gdy chcemy odseparować obiekty, od usług z których korzystają. Zwiększy to niezależność komponentów i ułatwi przyszłe wprowadzanie modyfikacji. Dodatkowo mając kilka podobnych serwisów, można wybierać któryś z nich przed uruchomieniem aplikacji przez odpowiednie ustawienia konfiguracyjne. Rozwiązanie takie wspiera testowalność.

Zasada działania:

Wprowadza się klasę lokatora usług, zawierającą statyczne pole typu HashTable z usługami. Kluczem do usługi w tablicy może być typ usługi lub podana nazwa. Dodatkowo klasa lokatora usług zawiera statyczne metody do pobrania i dodania nowego serwisu. Serwisy można dodawać na starcie aplikacji, np. w WPF czy Silverlight w metodzie OnStartup.

Przykład implementacyjny:

 public interface ILogger
    {
        void Write(string msg);
    }

    public class ConsoleLogger : ILogger
    {
        public void Write(string msg)
        {
            Console.WriteLine(msg);
        }
    }
internal class ServiceLocator
    {
        private static readonly Hashtable Services 
            = new Hashtable();

        public static void AddService<T>(T service)
        {
            Services.Add(typeof(T),service);
        }

        public static void AddService<T>(string name, T service)
        {
            Services.Add(name, service);
        }

        public static T GetService<T>()
        {
            return (T)Services[typeof(T)];
        }

        public static T GetService<T>(string name)
        {
            return (T)Services[name];
        }

    }
public class Fibonnaci
    {
        public int Number { get; set; }
        public int LastNum { get; set; }

        public Fibonnaci()
        {
            LastNum = 0;
            Number = 1;
        }

        public int Next()
        {
            var n = LastNum + Number;
            LastNum = Number;
            Number = n;
            IsDivisibleBy4();
            return n;
        }

        public void IsDivisibleBy4()
        {
            if (Number%4 != 0) return;
            var logger = ServiceLocator.GetService<ILogger>();
            logger.Write(String.Format
                             ("Number {0}, last : {1}",Number, LastNum));
        }
    }
class Program
    {
        static Program()
        {
            ServiceLocator.AddService<ILogger>(new ConsoleLogger());
        }

        static void Main(string[] args)
        {
            Fibonnaci fibonnaci = new Fibonnaci();
            for (int i = 0; i < 100; i++)
            {
                fibonnaci.Next();
            }
            Console.ReadKey();
        }
    }