2011-08-12

Pobranie wszystkich metod typu

Wszystkich czyli publicznych, chronionych, prywatnych. Także tych ukrytych. Pomijamy metody abstrakcyjne. Z nadpisanym metod wirtualnych zwracamy tylko te "najmłodsze" w hierarchii klas.

Możemy także zdecydować się czy odfiltrować metody generowane dla automatycznych właściwości.

Metody są zwracane w sposób posortowany najpierw po typie (od najmłodszego do bazowego), a dalej w kolejności występowania.

I znowu problemy. Tym razem przypuśćmy, że mamy metodę publiczną zdefiniowaną w klasie A, mamy klasę B wywodzącą się z A. Pobieramy tą metodę z typu A i z typu B, chcemy ustalić, że są takie same. Ich porównanie nic nie da. Ich ReflectedType są różne. Pozostaje więc ręcznie wykazać, że metody mają takie same sygnatury. Z całego dobrodziejswta MethodInfo decydujemy się nazwy i parametry. Niestety, okaże się parametrów też nie możemy porównać. Żeby było śmieszniej różnią się tylko MetadataToken. Z całego dobrodziejstwa ParameterInfo decydujemy się na porównanie ParameterType. Bez problemu radzimy sobie z rozróżnieniem parametrów z out, in, ref. Zgodnie z moimi testami wszystko działa. Ale nie wykluczam, że z uwagi na to, że do porównania nie biorę wszystkich elementów istnieją takie których porównanie zwróci fałszywy wynik.

Kod:
public static IEnumerable<type> GetBaseTypes(this Type a_type, 
    bool a_with_this = false)
{
    if (a_with_this)
        yield return a_type;

    Type t = a_type;

    while (t.BaseType != null)
    {
        t = t.BaseType;
        yield return t;
    }
}

public static IEnumerable<methodinfo> GetBaseDefinitions(this MethodInfo a_mi, 
    bool a_with_this = false)
{
    if (a_with_this)
        yield return a_mi;

    MethodInfo t = a_mi;

    while ((t.GetBaseDefinition() != null) && (t.GetBaseDefinition() != t))
    {
        t = t.GetBaseDefinition();
        yield return t;
    }
}

public static bool IsDerivedFrom(this MethodInfo a_mi, MethodInfo a_base,
    bool a_with_this = false)
{
    if (a_mi.Name != a_base.Name)
        return false;
    if (a_mi.DeclaringType == a_base.DeclaringType)
    {
        if (!a_mi.GetParameters().Select(p => p.ParameterType).SequenceEqual(
            a_base.GetParameters().Select(p => p.ParameterType)))
        {
            return false;
        }

        return a_with_this;
    }

    return a_mi.GetBaseDefinitions().Contains(a_base);
}

public static IEnumerable<MethodInfo> GetAllMethods(this Type a_type,
    bool a_include_autogenerated = false)
{
    List<MethodInfo> result = new List<MethodInfo>();

    foreach (var t in a_type.GetBaseTypes(true))
    {
        if (t == typeof(Object))
            break;
        if (t == typeof(ValueType))
            break;

        MethodInfo[] methods = t.GetMethods(
            BindingFlags.Public | BindingFlags.NonPublic |
            BindingFlags.Instance);

        foreach (var method in methods.Reverse())
        {
            if (method.IsDefined(typeof(CompilerGeneratedAttribute), true) &&
                !a_include_autogenerated)
            {
                continue;
            }

            if (method.IsAbstract)
                continue;
            if (result.All(m => !m.IsDerivedFrom(method, true)))
            {
                result.Add(method);
                yield return method;
            }
        }
    }
}

2011-08-11

Pobranie wszystkich właściwości i pól typu

Wszystkich czyli także tych ukrytych, prywatnych, z typu i wszystkich podtypów i co ważniejsze unikalnych. W przypadku właściwości nadpisanych wirtualnych zwracamy tylko jedną. Pomijamy właściwości abstrakcyjne.

Zarówno właściwości jak i pola staramy się zwrócić posortowane według hierarchii typów, a następnie posortowane według tego jak występują w Typie, co odzwierciedla ich porządek w kodzie.

W przypadku pól istnieje możliwość odfiltrowania pól dodanych przez właściwości automatyczne.

Główny problem to jak sprawdzić, że dana właściwość wywodzi się z innej. Nie ma czegoś takiego jak PropertyInfo.BaseProperty. Sprawdzenia dokonujemy dla metody ustawiającej i pobierającej właściwości. Przy czym nie wystarczy zwykłe porównanie z metodą bazową. Może się zdarzyć jeśli użyjemy właściwości automatycznych, że w hierarchii klas A->B->C, gdzie A ma właściwość abstrakcyjną, w B i C ją nadpisujemy to metody B i C jako bazową wskazują na A.

Sprawdzenie czy dwie właściwości są takie same też nie jest proste. Ze względu na możliwe różne ReflectedType porównanie wprost nie działa tak jak powinno. Porównujemy więc nazwę, typ właściwości i jej parametry indeksowe. Porównania parametrów indeksowych znowu nie możemy wprost, gdyż mogą się one różnić wartością MetadataToken. Porównujemy więc ich typy. Ponieważ do porównania nie bierzemy wszystkiego tylko kilka elementów nie jestem pewny na 100%, czy są jakieś przypadki które będą zakwalifikowane jako false positive albo false nagative.

private static IEnumerable<methodinfo> GetBaseDefinitions(this MethodInfo a_mi,
    bool a_with_this = false)
{
    if (a_with_this)
        yield return a_mi;

    MethodInfo t = a_mi;

    while ((t.GetBaseDefinition() != null) && (t.GetBaseDefinition() != t))
    {
        t = t.GetBaseDefinition();
        yield return t;
    }
}

private static bool ContainsAny<t>(this IEnumerable<t> a_enumerable, 
    IEnumerable<t> a_values)
{
    return a_enumerable.Intersect(a_values).Any();
}

private static bool IsAbstract(this PropertyInfo a_pi)
{
    if (a_pi.GetAccessors(true).Length == 0)
        return false;
    return a_pi.GetAccessors(true)[0].IsAbstract;
}

private static bool IsDerivedFrom(this PropertyInfo a_pi, PropertyInfo a_base,
    bool a_with_this = false)
{
    if (a_pi.Name != a_base.Name)
        return false;
    if (a_pi.PropertyType != a_base.PropertyType)
        return false;
    if (!a_pi.GetIndexParameters().Select(p => p.ParameterType).SequenceEqual(
        a_base.GetIndexParameters().Select(p => p.ParameterType)))
    {
        return false;
    }
    if (a_pi.DeclaringType == a_base.DeclaringType)
        return a_with_this;

    MethodInfo m1 = a_pi.GetGetMethod(true);
    MethodInfo m3 = a_base.GetGetMethod(true);

    if ((m1 != null) && (m3 != null))
    {
        if (m1.GetBaseDefinitions().ContainsAny(m3.GetBaseDefinitions(true)))
            return true;
    }
    else if ((m1 != null) || (m3 != null))
        return false;

    MethodInfo m2 = a_pi.GetSetMethod(true);
    MethodInfo m4 = a_base.GetSetMethod(true);

    if ((m2 != null) && (m4 != null))
    {
        if (m2.GetBaseDefinitions().ContainsAny(m4.GetBaseDefinitions(true)))
            return true;
    }
    else if ((m2 != null) || (m4 != null))
        return false;

    return false;
}

private static IEnumerable<type> GetBaseTypes(this Type a_type,
    bool a_with_this = false)
{
    if (a_with_this)
        yield return a_type;

    Type t = a_type;

    while (t.BaseType != null)
    {
        t = t.BaseType;
        yield return t;
    }
}

public static IEnumerable<propertyinfo> GetAllProperties(this Type type)
{
    List<propertyinfo> result = new List<propertyinfo>();

    foreach (var t in type.GetBaseTypes(true))
    {
        if (t == typeof(Object))
            break;
        if (t == typeof(ValueType))
            break;

        PropertyInfo[] type_props = t.GetProperties(
            BindingFlags.Public | BindingFlags.NonPublic |
            BindingFlags.Instance);

        foreach (var poss_prop in type_props.Reverse())
        {
            if (poss_prop.IsAbstract())
                continue;

            if (result.All(prop => !prop.IsDerivedFrom(poss_prop, true)))
                result.Add(poss_prop);
        }
    }

    result.Reverse();
    return result;
}

public static IEnumerable<fieldinfo> GetAllFields(this Type type,
    bool a_filter_autogenerated = true)
{
    foreach (var t in type.GetBaseTypes(true).Reverse())
    {
        if (t == typeof(Object))
            continue;
        if (t == typeof(ValueType))
            continue;

        FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public |
            BindingFlags.NonPublic);

        foreach (var field in fields)
        {
            if (field.IsDefined(typeof(CompilerGeneratedAttribute), false) &&
                a_filter_autogenerated)
            {
                continue;
            }

            if (field.DeclaringType == t)
                yield return field;
        }
    }
}

Sprawdzenie czy właściwość wywodzi się z innej właściwości.

Metoda ta była mi potrzebna do wyciągnięcia z klasy wszystkich właściwości. W tym celu trzeba przejść przez wszystkie podtypu typu dla którego wyciągamy informację, dla każdego z nich trzeba pobrać wszystkie właściwości i wykluczyć powtarzające.

Niestety PropertyInfo niosą bardzo mało informacji w sobie i trzeba odwoływać się do ich metod getter- i setter-a.

W pierwszym etapie sprawdzamy, czy te właściwości nie są przypadkiem takie same. Niestety z uwagi na ReflectedType nie możemy tak po prostu je porównać.

Dalej analizujemy zależności pomiędzy ich metodami ustawiającymi i pobierającymi. Do analizy zależności wykorzystujemy MetohodInfo.GetBaseDefinition (są dwie przeładowane wersje, ta druga z parametrem jest dla nas bardziej użyteczna, gdyż zwraca niepubliczne metody). I tutaj znowu niestety. Jeśli automatyczna właściwość jest zdefiniowana w klasie bazowej jako wirtualna i nadpisana w dwóch kolejnych klasach, to obie nadpisane właściwości wskazują poprzez MetohodInfo.GetBaseDefinitions swoich metod na metody właściwości w klasie wirtualnej. I to zmusiło mnie z prostego sprawdzania, czy MetohodInfo.GetBaseDefinitions jednej jest równe GetBaseDefinitions drugiej do sprawdzenia czy nie mają one jakiejś wspólnej metody bazowej.

Kod:
private static IEnumerable GetBaseDefinitions(this MethodInfo a_mi,
    bool a_with_this = false)
{
    if (a_with_this)
        yield return a_mi;
        
    MethodInfo t = a_mi;
        
    while ((t.GetBaseDefinition() != null) && (t.GetBaseDefinition() != t))
    {
        t = t.GetBaseDefinition();
        yield return t;
    }
}

private static bool ContainsAny(this IEnumerable a_enumerable, 
    IEnumerable a_values)
{
    return a_enumerable.Intersect(a_values).Any();
}

private static bool IsDerivedFrom(this PropertyInfo a_pi, PropertyInfo a_base,
    bool a_with_this = false)
{
    if (a_pi.Name != a_base.Name)
        return false;
    if (a_pi.PropertyType != a_base.PropertyType)
        return false;
    if (!a_pi.GetIndexParameters().SequenceEqual(a_base.GetIndexParameters()))
        return false;
    if (a_pi.DeclaringType == a_base.DeclaringType)
        return a_with_this;
        
    MethodInfo m1 = a_pi.GetGetMethod(true);
    MethodInfo m3 = a_base.GetGetMethod(true);
        
    if ((m1 != null) && (m3 != null))
    {
        if (m1.GetBaseDefinitions().ContainsAny(m3.GetBaseDefinitions(true)))
            return true;
    }
    else if ((m1 != null) || (m3 != null))
        return false;
        
    MethodInfo m2 = a_pi.GetSetMethod(true);
    MethodInfo m4 = a_base.GetSetMethod(true);
        
    if ((m2 != null) && (m4 != null))
    {
        if (m2.GetBaseDefinitions().ContainsAny(m4.GetBaseDefinitions(true)))
            return true;
    }
    else if ((m2 != null) || (m4 != null))
        return false;
        
    return false;
}

Ustalanie modyfikatora właściwości i metod

Metody i własciwości mogą być oznaczone słowami kluczowymi virtual, override, abstract. Jak wykorzystując refleksję dobrać się do tych danych ?

Nie jest to niestety takie proste. Dla metod mamy dostępne właściwości MethodInfo.IsVirtual, MethodInfo.IsAbstract.

Informacje o właściwościach są bardzo ubogie. Całe wnioskowanie musimy przeprowadzić w oparciu o metody PropertyInfo.GetGetMethod i PropertyInfo.GetSetMethod do których dostęp możemy też uzyskać poprzez PropertyInfo.GetAccessors. Wszystkie trzy metody mają przeładowaną wersję za pomocą których dobieramy się do metod niepublicznych. Tak więc analiza właściwości sprowadza się do analizy jednej z jej metod: getter-a albo setter-a. Nie trzeba analizować obu gdyż modyfikatory typu virtual, override, abstract są nakładane na właściwość.

Do ustalenia metody bazowej służy MethodInfo.GetBaseDefinition. W przypadku braku takowej funkcja nie zwraca null tylko samą siebie.

Do ustalenia typu w którym metoda została zdefiniowana służy MethidInfo.DeclaringType. Jest jeszcze MethidInfo.ReflectedType, który zwraca typ z którego dana metoda została pobrana. Np. jeśli jest to metoda publiczna zdefiniowana w klasie bazowej A, a pobrana z klasy podrzędnej B to te dwie wartości będą różne. Co więcej porównanie metod da inne wyniki.

Całość ogólnie tworzy mętny obraz. Sam nie wiem czy podane niżej rozwiązanie obejmuje wszystkie przypadki i czy nie ma jakiś wyjątków. Cała refleksja w C# jest potężna, ale jak w nią wnikamy pojawia się mnóstwo problemów. Brakuje podanych niżej metod. Metod na porównanie czy elementy są takie same, niezależnie od różnicy ReflectedType, brakuje rozbudowanej PropertyInfo, tak by nie trzeba było analizować jej metody getter-a i setter-a. Bardzo trudno ustalić, że metoda interfejsu została zadeklarowana bez użycia virtual. Bardzo trudno ustalić czy metoda została zadeklarowana przy pomocy new. Nie da się w żaden sposób powiązać właściwości automatycznej z polem prywatnym. I pewnie jeszcze wiele innych.

Moja wersja metod, która na obecny czas działają:
/// 
/// With virtual keyword. Also includes interface implementations even without 
/// virtual keyword.
/// 
/// /// 
public static bool IsVirtual(this MethodInfo a_mi)
{
    return a_mi.IsVirtual && !a_mi.IsAbstract && !a_mi.IsOverriden();
}

/// 
/// With abstract keyword.
/// 
/// /// 
public static bool IsAbstract(this MethodInfo a_mi)
{
    return a_mi.IsAbstract;
}

/// 
/// With override keyword.
/// 
/// /// 
public static bool IsOverriden(this MethodInfo a_mi)
{
    return a_mi.DeclaringType != a_mi.GetBaseDefinition().DeclaringType;

}

/// 
/// With virtual keyword. Also includes interface implementations 
/// even without virtual keyword.
/// 
/// /// 
public static bool IsVirtual(this PropertyInfo a_pi)
{
    if (a_pi.GetAccessors(true).Length == 0)
        return false;

    return a_pi.GetAccessors(true)[0].IsVirtual();
}

/// 
/// With abstract keyword.
/// 
/// /// 
public static bool IsAbstract(this PropertyInfo a_pi)
{
    if (a_pi.GetAccessors(true).Length == 0)
        return false;
    return a_pi.GetAccessors(true)[0].IsAbstract();
}

/// 
/// With override keyword.
/// 
/// /// 
public static bool IsOverriden(this PropertyInfo a_pi)
{
    if (a_pi.GetAccessors(true).Length == 0)
        return false;

    return a_pi.GetAccessors(true)[0].IsOverriden();
}