Easiest way to get a common base class from a collection of types

后端 未结 6 1538
情书的邮戳
情书的邮戳 2020-12-17 05:07

I\'m building a custom property grid that displays the properties of items in a collection. What I want to do is show only the properties in the grid that are common amongst

相关标签:
6条回答
  • 2020-12-17 05:27

    The code posted to get the most-specific common base for a set of types has some issues. In particular, it breaks when I pass typeof(object) as one of the types. I believe the following is simpler and (better) correct.

    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;
    }
    

    I also tested with:

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

    And got typeof(DbCommand). And with:

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

    And got typeof(Compoment). And with:

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

    And got typeof(MarshalByRefObject). And with

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

    And got typeof(object).

    0 讨论(0)
  • 2020-12-17 05:36

    Here is a way to get the common set of properties from a list of types:

    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;
        }
    }
    

    This iterates over all the properties in all the types and only ends up with those occurring a number of times equal to the number of types.

    If you prefer compact LINQ queries you can use the following equivalent expression:

    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();
    
    0 讨论(0)
  • 2020-12-17 05:39

    To get the common properties from a collection of Objects, you can use a method like this:

    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();
    }
    

    Then, once you have the string of a property you want to do something with, you can take any of the Objects in the collection and use reflection to call that Property by its String Name.

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

    Similarly, the code in the GetCommonPropertiesByName method can be modified to get common members, methods, nested types, fields, etc...

    0 讨论(0)
  • 2020-12-17 05:45

    You can do this with a method that keeps checking for common base classes. I wrote up this, quickly, using the BaseClass feature of the Type class. You don't have to use an array, a list or other IEnumerable can work with small modifications to this.

    I tested it with:

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

    And got the right answer of DbCommand. Here is my code.

        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;
        }
    
    0 讨论(0)
  • 2020-12-17 05:45

    I use something like this but Tony's answer is probably better:

    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;
        }
    }
    
    0 讨论(0)
  • 2020-12-17 05:51

    Well,

    You could create in interface similar to IComparable but instead call it something like IPropertyComparable and then have the classes that implement it use reflection to compare their property names as so...

    public int Compare(T x, T y)
    {
         PropertyInfo[] props = x.GetType().GetProperties();
    
         foreach(PropertyInfo info in props)
         {
              if(info.name == y.GetType().Name)
              ....
         }
    
         ...
    

    I'll let you figure out the rest. It could probably be a little more elegant anyway, use LINQ maybe...

    • Matt
    0 讨论(0)
提交回复
热议问题