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

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, 6 kwietnia 2013

[IoC] Kontenery IoC

Kontenerem IoC można w skrócie nazwać pewien mechanizm wykonujący wstrzykiwanie zależności (Dependency Injection). Funkcjonalnością kontenera musi być możliwość konfiguracji zależności jakie zachodzą między obiektami. Kiedy prosimy kontener o typ podając mu na przykład interfejs, wie co ma nam zwrócić, a także wie jakie zależności powinien dany obiekt posiadać (czym różni się od wzorca ServiceLocatora).

Przykład zależności pomiędzy obiektami:

public class AudiA4
    {
        private readonly IEngine _engine;

        public AudiA4(IEngine engine)
        {
            _engine = engine;
        }

        public void Run()
        {
            _engine.Run();
        }
    }

    public interface IEngine
    {
        void Run();
    }

    public class DieselEngine : IEngine
    {
        public void Run()
        {
            Console.Write("Diesel engine running...");
        }
    }

Kontener tworzą dwie metody. Pierwsza pozwala na rejestrowanie typów w słowniku. Typy rejestrowane powinny być po interfejsach tylko wtedy jeżeli implementujemy dany interfejs więcej niż raz. W przeciwnym wypadku tworzymy niepotrzebne byty. Druga metoda tworzy obiekt na podstawie wcześniej skonfigurowanych zależności. W C# możemy skorzystać z mechanizmu Reflection, przez co zajmuje nam to bardzo mało kodu. Ponieważ w bardziej rozbudowany aplikacjach możemy mieć wielopoziomowe grafy obiektów, metodę Resolve wołamy rekurencyjnie, aby rozwiązać wszystkie zależności.


public class IocContainer
{
    private Dictionary<Type, Type> dependencyMap;

    public IocContainer()
    {
        dependencyMap = new Dictionary<Type, Type>();
    }

    public T Resolve<T>()
    {
        return (T) Resolve(typeof(T));
    }

    private object Resolve(Type type)
    {
        Type resolvedType = null;
        try
        {
            resolvedType = dependencyMap[type];
        }
        catch
        {
            throw new KeyNotFoundException(String.Format("Cannot resolve type {0}", type.FullName));
        }
        var ctor = resolvedType.GetConstructors().First();
        var parameters = ctor.GetParameters();
        if (parameters.Count() == 0)
            return Activator.CreateInstance(resolvedType);
        IList<object> @params = new List<object>();
        foreach (var parameter in parameters)
        {
            @params.Add(Resolve(parameter.ParameterType));
        }
        return ctor.Invoke(@params.ToArray());
    }

    public void Register<TFrom, TTo>()
    {
        dependencyMap.Add(typeof(TFrom), typeof(TTo));
    }
}

Przykład użycia kontenera:

static void Main(string[] args)
{
    var container = new IocContainer();
    container.Register<AudiA4, AudiA4>();
    //zależnie od konfiguracji
    container.Register<IEngine, DieselEngine>();
    var car = container.Resolve<AudiA4>();
    car.Run();
}

piątek, 2 listopada 2012

[C#|Visual Studio] DefaultValueAttribute

Jednym z atrybutów, jakie udostępnia System.ComponentModel jest DefaultValueAttribute. Wbrew pozorom podanie wartości jako argument konstruktora nie wystarczy, aby ustawić domyślną wartość property. Dokumentacja MSDN jasno mówi, że programista musi sam ustawić domyślne wartości, np. w konstruktorze. Aby wykorzystać możliwości DefaultValueAttribute można skorzystać z mechanizmu reflection.

Zatem poniższy kod, pomimo swojej przejrzystości nie wystarczy, aby ustawić domyślne wartości dla properties.

using System;
using CM = System.ComponentModel;

namespace DefaultValueAttribute
{
    public class MySimpleClass
    {
        [CM.DefaultValue(true)]
        public bool MyBoolProperty { get; set; }

        [CM.DefaultValue(8)]
        public int MyIntProperty { get; set; }

        [CM.DefaultValue("Default text")]
        public string MyStringProperty { get; set; }
    }
}

Aby móc skorzystać z możliwości ustawiania wartości domyślnych poprzez atrybuty, wystarczy skorzystać z poniższej ExtensionMethod.

public static class Extensions
    {
        public static void ApplyDefaultValues(this object obj)
        {
             foreach (CM.PropertyDescriptor prop in CM.TypeDescriptor.GetProperties(obj))
             {
                 var attr = (CM.DefaultValueAttribute) prop.Attributes[typeof (CM.DefaultValueAttribute)];
                 {
                     prop.SetValue(obj, attr.Value);
                 }
             }
        }
    }

Wywoływać ją można np. w konstruktorze.