środa, 4 lipca 2012

Reflection - dostęp do właściwości klasy.

Spotkałem się ostatnio z koniecznością użycia właściwości z obiektów różnych typów, nie mających ze sobą nic wspólnego. Każdy z tych typów mógł posiadać właściwość "Synchronized" typu enum, w przypadku gdyby taka "property" istniała miałem pobrać jej wartość i po porównaniu z wartością wzorcową ustawić tą wartość na nową.
Do takich zadań możemy użyć funkcjonalności z "Reflection".
W namespace System.Reflection znajdują sie klasy umożliwiające pobieranie wszelakich danych z kodu zarządzanego na podstawie "metadanych" a także manipulacje instancjami typów - jak pobieranie oraz zmiana wartości co właśnie chciałem dziś przedstawić.

Tak więc zacznę od metody "GetProperty":
            PropertyInfo result = typeof(T).GetProperty(propertyName, typeof(TProperty));
Powyższy kawałek kodu zwraca nam klasę "PropertyInfo" opisującą parametry publicznej właściwości o nazwie zdefiniowanej za pomocą zmiennej "propertyName" oraz typie "TProperty". Właściwość ta znajdować ma się obiekcie typu "T".
W przypadku jeżeli typ "T" nie posiada właściwości spełniającej takie kryteria metoda zwraca "null".
Aby zilustrować działanie metody "GetProperty" oraz problem, który należy rozwiązać zdefiniujmy klasy:
        interface IA
        {
            int Props { get; set; }
        }
        interface IB : IA
        {
        }
        class A : IA
        {
            public int Props {get; set;}
            private int prProps { get; set; }
        }
        class B : IB
        {
            public int Props { get; set; }
        }
Z kody wynika, że zarówno klasy jak i interfejsy mają właściwość "Props".
        [TestMethod()]
        public void GetPropertyTest()
        {
            PropertyInfo result = typeof(A).GetProperty("Props", typeof(int));
            Assert.IsNotNull(result);

            result = typeof(A).GetProperty("prProps", typeof(int));
            Assert.IsNull(result);

            result = typeof(IA).GetProperty("Props", typeof(int));
            Assert.IsNotNull(result);

            result = typeof(B).GetProperty("Props", typeof(int));
            Assert.IsNotNull(result);

            result = typeof(IB).GetProperty("Props", typeof(int));
            Assert.IsNotNull(result);//result is null!!!
        }
Jednak test jednostkowy sprawdzający działanie "GetProperty" na zdefiniowanych typach zwraca nieoczekiwaną wartość "null" przy interfejsie "IB".
Problem wynika stąd, że właściwość "Props" w interfejsie "IB" jest właściwością dziedziczoną po "IA".
Problem można rozwiązać za pomocą funkcji, która sprawdzi wszystkie interfejsy:
  
        public static PropertyInfo GetProperty<T, TProperty>(string propertyName)
        {
            Type type = typeof(T);
            PropertyInfo result = type.GetProperty(propertyName, typeof(TProperty));
            if (result != null)
                return result;
            if (type.IsInterface)
            {
                foreach (Type interfaceTypes in type.GetInterfaces())
                {
                    result = interfaceTypes.GetProperty(propertyName, typeof(TProperty));
                    if (result != null)
                        return result;
                }
            }
            return null;
        }
Gdy już mamy funkcję znajdującą "property" dla typu, możemy skorzystać z niej pisząc metody umożliwiające pobranie wartości "GetValue" oraz jej zmianę "SetValue":
        public static TProperty GetValue<T, TProperty>(T obj, string propertyName)
        {
            PropertyInfo pi = GetProperty<T, TProperty>(propertyName);
            if (pi != null)
                return (TProperty)pi.GetValue(obj, null);
            return default(TProperty);
        }
        public static void SetValue<T, TProperty>(T obj, string propertyName, TProperty newValue)
        {
            Type type = typeof(T);
            PropertyInfo pi = GetProperty<T, TProperty>(propertyName);
            if (pi != null)
                pi.SetValue(obj, newValue, null);
        }
I to by było na razie tyle tytułem wstępu do rozległego tematu jakim jest "Reflection".

Brak komentarzy:

Prześlij komentarz