La forma más sencilla de obtener una clase base común a partir de una colección de tipos

votos
3

Estoy construyendo una grilla de propiedad personalizada que muestra las propiedades de los elementos en una colección. Lo que quiero hacer es mostrar solo las propiedades en la grilla que son comunes entre cada elemento. Supongo que la mejor manera de hacerlo sería encontrar la clase base común de cada tipo en la colección y mostrar sus propiedades. ¿Hay alguna manera más fácil? ¿Me puede dar un ejemplo de código del mejor enfoque para hacer esto?

Publicado el 09/12/2008 a las 17:44
fuente por usuario
En otros idiomas...                            


6 respuestas

votos
3

Puede hacer esto con un método que siga buscando clases base comunes. Escribí esto rápidamente, usando la función BaseClass de la clase Type. No tiene que usar una matriz, una lista u otra IEnumerable puede funcionar con pequeñas modificaciones a esto.

Lo probé con:

static void Main(string[] args)
{
    Console.WriteLine("Common Types: " + GetCommonBaseClass(new Type[] {typeof(OleDbCommand), typeof(OdbcCommand), typeof(SqlCommand)}).ToString());   
}

Y obtuve la respuesta correcta de DbCommand. Aquí está mi código.

    static Type GetCommonBaseClass(Type[] types)
    {
        if (types.Length == 0)
            return (typeof(object));
        else if (types.Length == 1)
            return (types[0]);

        // Copy the parameter so we can substitute base class types in the array without messing up the caller
        Type[] temp = new Type[types.Length];

        for (int i = 0; i < types.Length; i++)
        {
            temp[i] = types[i];
        }

        bool checkPass = false;

        Type tested = null;

        while (!checkPass)
        {
            tested = temp[0];

            checkPass = true;

            for (int i = 1; i < temp.Length; i++)
            {
                if (tested.Equals(temp[i]))
                    continue;
                else
                {
                    // If the tested common basetype (current) is the indexed type's base type
                    // then we can continue with the test by making the indexed type to be its base type
                    if (tested.Equals(temp[i].BaseType))
                    {
                        temp[i] = temp[i].BaseType;
                        continue;
                    }
                    // If the tested type is the indexed type's base type, then we need to change all indexed types
                    // before the current type (which are all identical) to be that base type and restart this loop
                    else if (tested.BaseType.Equals(temp[i]))
                    {
                        for (int j = 0; j <= i - 1; j++)
                        {
                            temp[j] = temp[j].BaseType;
                        }

                        checkPass = false;
                        break;
                    }
                    // The indexed type and the tested type are not related
                    // So make everything from index 0 up to and including the current indexed type to be their base type
                    // because the common base type must be further back
                    else
                    {
                        for (int j = 0; j <= i; j++)
                        {
                            temp[j] = temp[j].BaseType;
                        }

                        checkPass = false;
                        break;
                    }
                }
            }

            // If execution has reached here and checkPass is true, we have found our common base type, 
            // if checkPass is false, the process starts over with the modified types
        }

        // There's always at least object
        return tested;
    }
Respondida el 09/12/2008 a las 18:13
fuente por usuario

votos
2

El código publicado para obtener la base común más específica para un conjunto de tipos tiene algunos problemas. En particular, se rompe cuando paso typeof (objeto) como uno de los tipos. Creo que lo siguiente es más simple y (mejor) correcto.

public static Type GetCommonBaseClass (params Type[] types)
{
    if (types.Length == 0)
        return typeof(object);

    Type ret = types[0];

    for (int i = 1; i < types.Length; ++i)
    {
        if (types[i].IsAssignableFrom(ret))
            ret = types[i];
        else
        {
            // This will always terminate when ret == typeof(object)
            while (!ret.IsAssignableFrom(types[i]))
                ret = ret.BaseType;
        }
    }

    return ret;
}

También probé con:

Type t = GetCommonBaseClass(typeof(OleDbCommand),
                            typeof(OdbcCommand),
                            typeof(SqlCommand));

Y tengo typeof(DbCommand). Y con:

Type t = GetCommonBaseClass(typeof(OleDbCommand),
                            typeof(OdbcCommand),
                            typeof(SqlCommand),
                            typeof(Component));

Y tengo typeof(Compoment). Y con:

Type t = GetCommonBaseClass(typeof(OleDbCommand),
                            typeof(OdbcCommand),
                            typeof(SqlCommand),
                            typeof(Component),
                            typeof(Component).BaseType);

Y tengo typeof(MarshalByRefObject). Y con

Type t = GetCommonBaseClass(typeof(OleDbCommand),
                            typeof(OdbcCommand),
                            typeof(SqlCommand),
                            typeof(Component),
                            typeof(Component).BaseType,
                            typeof(int));

Y tengo typeof(object).

Respondida el 31/03/2009 a las 17:26
fuente por usuario

votos
2

Para obtener las propiedades comunes de una colección de Objetos, puede usar un método como este:

public static String[] GetCommonPropertiesByName(Object[] objs)
{
    List<Type> typeList = new List<Type>(Type.GetTypeArray(objs));
    List<String> propertyList = new List<String>();
    List<String> individualPropertyList = new List<String>();

    foreach (Type type in typeList)
    {
        foreach (PropertyInfo property in type.GetProperties())
        {
            propertyList.Add(property.Name);
        }
    }

    propertyList = propertyList.Distinct().ToList();

    foreach (Type type in typeList)
    {
        individualPropertyList.Clear();

        foreach (PropertyInfo property in type.GetProperties())
        {
            individualPropertyList.Add(property.Name);
        }

        propertyList = propertyList.Intersect(individualPropertyList).ToList();
    }

    return propertyList.ToArray();
}

Luego, una vez que tenga la cadena de una propiedad con la que desea hacer algo, puede tomar cualquiera de los objetos en la colección y usar la reflexión para llamar a esa propiedad por su nombre de cadena.

PropertyInfo p = t.GetType().GetProperty("some Property String Name");
p.GetValue(t, null);
p.SetValue(t, someNewValue, null);

Del mismo modo, el código del GetCommonPropertiesByNamemétodo se puede modificar para obtener miembros comunes, métodos, tipos anidados, campos, etc.

Respondida el 09/12/2008 a las 19:27
fuente por usuario

votos
0

Uso algo como esto, pero la respuesta de Tony es probablemente mejor:

internal class BaseFinder
{
    public static Type FindBase(params Type[] types)
    {
        if (types == null)
            return null;

        if (types.Length == 0)
            return null;

        Dictionary<Type, IList<Type>> baseTypeMap = new Dictionary<Type,IList<Type>>();

        // get all the base types and note the one with the longest base tree
        int maxBaseCount = 0;
        Type typeWithLongestBaseTree = null;
        foreach (Type type in types)
        {
            IList<Type> baseTypes = GetBaseTree(type);
            if (baseTypes.Count > maxBaseCount)
            {
                typeWithLongestBaseTree = type;
                maxBaseCount = baseTypes.Count;
            }
            baseTypeMap.Add(type, baseTypes);
        }

        // walk down the tree until we get to a common base type
        IList<Type> longestBaseTree = baseTypeMap[typeWithLongestBaseTree];
        for (int baseIndex = 0; baseIndex < longestBaseTree.Count;baseIndex++)
        {
            int commonBaseCount = 0;
            foreach (Type type in types)
            {
                IList<Type> baseTypes = baseTypeMap[type];
                if (!baseTypes.Contains(longestBaseTree[baseIndex]))
                    break;
                commonBaseCount++;
            }
            if (commonBaseCount == types.Length)
                return longestBaseTree[baseIndex];
        }
        return null;
    }

    private static IList<Type> GetBaseTree(Type type)
    {
        List<Type> result = new List<Type>();
        Type baseType = type.BaseType;
        do
        {
            result.Add(baseType);
            baseType = baseType.BaseType;
        } while (baseType != typeof(object));
        return result;
    }
}
Respondida el 10/12/2008 a las 12:50
fuente por usuario

votos
0

Aquí hay una manera de obtener el conjunto común de propiedades de una lista de tipos:

class TypeHandler
{
    public static List<string> GetCommonProperties(Type[] types)
    {
        Dictionary<string, int> propertyCounts = new Dictionary<string, int>();

        foreach (Type type in types)
        {
            foreach (PropertyInfo info in type.GetProperties())
            {
                string name = info.Name;
                if (!propertyCounts.ContainsKey(name)) propertyCounts.Add(name, 0);
                propertyCounts[name]++;
            }
        }

        List<string> propertyNames = new List<string>();

        foreach (string name in propertyCounts.Keys)
        {
            if (propertyCounts[name] == types.Length) propertyNames.Add(name);
        }

        return propertyNames;
    }
}

Esto itera sobre todas las propiedades en todos los tipos y solo termina con las que ocurren varias veces igual a la cantidad de tipos.

Si prefiere consultas compactas LINQ, puede usar la siguiente expresión equivalente:

return (from t in types
              from p in t.GetProperties()
              group p by p.Name into pg
              where pg.Count() == types.Length
              select pg.Key).ToList();
Respondida el 09/12/2008 a las 19:02
fuente por usuario

votos
0

Bien,

Puede crear una interfaz similar a IComparable pero en su lugar llamarla IPropertyComparable y luego hacer que las clases que la implementan usen la reflexión para comparar sus nombres de propiedad como tal ...

public int Compare(T x, T y)
{
     PropertyInfo[] props = x.GetType().GetProperties();

     foreach(PropertyInfo info in props)
     {
          if(info.name == y.GetType().Name)
          ....
     }

     ...

Dejaré que descubras el resto. Probablemente podría ser un poco más elegante de todos modos, use LINQ tal vez ...

  • Mate
Respondida el 09/12/2008 a las 18:00
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more