Paweł Łukasiewicz
2015-04-02
Paweł Łukasiewicz
2015-04-02
Udostępnij Udostępnij Kontakt

Refleksja służy to uzyskania informacji o typie w trakcie wykonywania programu. Klasy, które mają dostęp do metadanych działającego programu są zdefiniowane w przestrzeni nazw System.Reflection.

Przestrzeń nazw System.Reflection zawiera klasy, które pozwalają na uzyskanie informacji o aplikacji oraz pozwalają na dynamiczne dodawanie typów, wartości i obiektów do aplikacji.


Możliwości refleksji

Wykorzystanie refleksji pozwala na:

  • podgląd atrybutów w trakcie wykonywania programu;
  • sprawdzenie różnych typów danych w danej bibliotece oraz utworzenie ich instancji;
  • wykonanie późnego wiązania do metod i właściwości (późne wiązanie oznacza, że np. docelowa metoda jest poszukiwana w trakcie wykonywania programu. Wiązanie takie ma zwykle wpływ na wydajność. Poszukiwanie takie wymaga dopasowania w trakcie wykonywania programu, oznacza to, że wywołania metod są wolniejsze. Przeciwieństwem jest wczesne wiązanie, tj. docelowa metoda jest znana już w trakcie kompilacji kodu);
  • tworzenie nowych typów w trakcie wykonywania programu a następnie wykonywanie różnych zadań przy użyciu tych typów.


Wyświetlanie metadanych

W poprzednim rozdziale wspomniałem, że przy użyciu refleksji można wyświetlać informacje o atrybutach.

Obiekt MemberInfo pochodzący z klasy System.Reflection musi być zainicjowany, aby poznać informację o atrybutach danych klasy. Aby to zrobić należy zdefiniować obiekt klasy docelowej:

System.Reflection.MemberInfo info = typeof(nazwa_klasy_do_zbadania);
Przykład użycia refleksji:
using System;
namespace Refleksja
{
    class Program
    {
        static void Main(string[] args)
        {
            System.Reflection.MemberInfo info = typeof(MyClassToGetAttributeInfo);
            // pobranie listy atrybutów
            object[] attributes = info.GetCustomAttributes(true);
            for (int i = 0; i < attributes.Length; i++)
            {
                // Wypisujemy wszystkie atrybuty
                Console.WriteLine(attributes[i]);
                // Dodatkowo uzyskamy dostęp do opisu naszego atrybutu
                ExampleAttribute ea = (ExampleAttribute)attributes[i];
                Console.WriteLine("Info: {0}", ea.message);
            }
            Console.ReadKey();
            // Wynik działania programu
            // Refleksja.ExampleAttribute
            // Info: Informacja o mojej klasie
        }
    }
    [AttributeUsage(AttributeTargets.All)]
    public class ExampleAttribute : Attribute
    {
        public readonly string message;
        private string topic;
        public ExampleAttribute(string Message)
        {
            this.message = Message;
        }
        public string Topic
        {
            get
            {
                return topic;
            }
            set
            {
                topic = value;
            }
        }
    }
    [ExampleAttribute("Informacja o mojej klasie")]
    class MyClassToGetAttributeInfo
    {
    }
}
Refleksja dla przykładu z poprzedniego rozdziału:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace RefleksjaWlasnyAtrybut
{
    class Program
    {
        static void Main(string[] args)
        {
            Rectangle r = new Rectangle(2.5, 3.5);
            Type type = typeof(Rectangle);
            // W pierwszym kroku sprawdzimy atrybuty klasy
            Console.WriteLine("Atrybuty klasy:");
            foreach (Object item in type.GetCustomAttributes(false))
            {
                DebugInfo di = item as DebugInfo;
                if (di != null)
                {
                    Console.WriteLine("Numer błędu: {0}", di.CodeNumber);
                    Console.WriteLine("Programista: {0}", di.DeveloperName);
                    Console.WriteLine("Przegląd kodu: {0}", di.LastReviewData);
                    Console.WriteLine("Info: {0}", di.message);
                }
            }
            Console.WriteLine();
            Console.WriteLine("Atrybuty metod:");
            // W pierwszej kolejności pobieramy wszystkie metody
            foreach (MethodInfo method in type.GetMethods())
            {
                foreach (Attribute a in method.GetCustomAttributes(true))
                {
                    DebugInfo di = a as DebugInfo;
                    if (di != null)
                    {
                        Console.WriteLine("Numer błędu: {0}", di.CodeNumber);
                        Console.WriteLine("Programista: {0}", di.DeveloperName);
                        Console.WriteLine("Przegląd kodu: {0}", di.LastReviewData);
                        Console.WriteLine("Info: {0}", di.message);
                    }
                }
            }
            Console.ReadKey();
            // Wynik działania programu
            // Atrybuty klasy:
            // Numer bledu: 23
            // Programista: Pawel
            // Przeglad kodu: 27/11/2015
            // Info: Zly zwracany typ
            // Numer bledu: 223
            // Programista: Pawel
            // Przeglad kodu: 29/11/2015
            // Info: Nieprzypisana wartosc
            // Atrybuty metod:
            // Numer bledu: 11
            // Programista: Pawel
            // Przeglad kodu: 22/11/2015
            // Info: Zly zwracany typ
        }
    }
    [AttributeUsage(AttributeTargets.Class |
        AttributeTargets.Constructor |
        AttributeTargets.Method |
        AttributeTargets.Field |
        AttributeTargets.Property,
        AllowMultiple = true)]
    public class DebugInfo : Attribute
    {
        // pola prywatne
        private int codeNumber;
        private string developerName;
        private string lastReviewData;
        public string message;
        public DebugInfo(int code, string dev, string d)
        {
            this.codeNumber = code;
            this.developerName = dev;
            this.lastReviewData = d;
        }
        // właściowości, które dają nam dostęp do pól prywatnych
        // ich użycie będzie potrzebne podczas korzystania z mechanizmu refleksji
        // ale o tym w kolejnym rozdziale
        public int CodeNumber
        {
            get
            {
                return codeNumber;
            }
        }
        public string DeveloperName
        {
            get
            {
                return developerName;
            }
        }
        public string LastReviewData
        {
            get
            {
                return lastReviewData;
            }
        }
    }
    [DebugInfo(23, "Paweł", "27/11/2015", message = "Zły zwracany typ")]
    [DebugInfo(223, "Paweł", "29/11/2015", message = "Nieprzypisana wartość")]
    class Rectangle
    {
        protected double length;
        protected double width;
        public Rectangle(double l, double w)
        {
            length = l;
            width = w;
        }
        [DebugInfo(11, "Paweł", "22/11/2015", message = "Zły zwracany typ")]
        public double GetArea()
        {
            return length * width;
        }
    }
}

Instancja klasy

Do tej pory omawiając refleksję chcieliśmy uzyskać dostęp jedynie do atrybutów i informacji o nich. W poniższym przykładzie omówimy sposób utworzenia instancji danej klasy a także dostęp do właściwości oraz wywołamy metody tej klasy.

using System;
using System.Reflection;
namespace RefleksjaInstancjaKlasy
{
    class Program
    {
        static void Main(string[] args)
        {
            var dog = Activator.CreateInstance(typeof(Dog)) as Dog;
            // Dostęp do właściowości klasy
            PropertyInfo[] properties = dog.GetType().GetProperties();
            PropertyInfo prep1 = properties[0];
            PropertyInfo prep2 = properties[1];
            // Ustawiamy wartość poszczególnych pól
            prep1.SetValue(dog, 3);
            prep2.SetValue(dog, "wilczur");
            // Wyświetlenie tych wartości
            Console.WriteLine(prep1.GetValue(dog, null));
            Console.WriteLine(prep2.GetValue(dog, null));
            // Dostęp do konstruktora
            var defaultConstr = typeof(Dog).GetConstructor(new Type[0]);
            var paramConstr = typeof(Dog).GetConstructor(new[] { typeof(int) });
            // Wywołanie konstruktorów
            var defConstrTest = (Dog)defaultConstr.Invoke(null);
            var secConstrTest = (Dog)paramConstr.Invoke(new object[] { 45 });
            // Wyświetlenie wartości po wywołaniu konstruktorów
            Console.WriteLine("Pierwszy konstruktor, liczba nóg: {0}", defConstrTest.NumberOfLegs);
            Console.WriteLine("Drugi konstruktor, liczba nóg: {0}", secConstrTest.NumberOfLegs);
            // Dostęp do metod
            var TestMethod = typeof(Dog).GetMethod("SetDogsBread");
            var InvokeMethod = (Dog)TestMethod.Invoke(dog, new object[] { "Mieszaniec" });
            Console.ReadKey();
            
            // Wynik działania programu
            // 3
            // wilczur
            // Pierwszy konstruktor, liczba nóg: 0
            // Drugi konstruktor, liczba nóg: 45
            // Refleksja! - Rasa psa: Mieszaniec
        }
    }
    class Dog
    {
        public int NumberOfLegs { get; set; }
        public string Breed { get; set; }
        public Dog()
        {
        }
        public Dog(int number)
        {
            NumberOfLegs = number;
        }
        public void SetDogsBread(string breed)
        {
            this.Breed = breed;
            Console.WriteLine("Refleksja! - Rasa psa: {0}", breed);
        }
        public void SetLegsNumber(int number)
        {
            this.NumberOfLegs = number;
        }
        public void Display()
        {
            Console.Write("Liczba nóg: {0}", NumberOfLegs);
            Console.WriteLine("Rasa psa: {0}", Breed);
        }
    }
}