【问题标题】:Easiest way to get a common base class from a collection of types从类型集合中获取公共基类的最简单方法
【发布时间】:2008-12-09 16:44:01
【问题描述】:

我正在构建一个自定义属性网格,用于显示集合中项目的属性。我想要做的是只显示网格中每个项目共有的属性。我假设最好的方法是找到集合中每种类型的公共基类并显示它的属性。有没有更简单的方法?您能给我一个代码示例来说明执行此操作的最佳方法吗?

【问题讨论】:

    标签: c# .net linq reflection


    【解决方案1】:

    您可以使用不断检查公共基类的方法来做到这一点。我使用 Type 类的 BaseClass 特性快速地写下了这篇文章。您不必使用数组、列表或其他 IEnumerable 可以对它进行小的修改。

    我测试过:

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

    并得到了 DbCommand 的正确答案。这是我的代码。

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

    【讨论】:

      【解决方案2】:

      为获取一组类型的最具体的公共基础而发布的代码存在一些问题。特别是,当我将 typeof(object) 作为其中一种类型传递时,它会中断。我相信以下内容更简单且(更好)正确。

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

      我还测试了:

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

      得到typeof(DbCommand)。还有:

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

      得到了typeof(Compoment)。还有:

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

      得到typeof(MarshalByRefObject)。和

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

      得到typeof(object)

      【讨论】:

      • 对我来说似乎很奇怪,当你得到 null 或 [ ..., null, ...] 时你会返回 typeof(object)。在这种情况下,我宁愿返回 null。
      【解决方案3】:

      要从对象集合中获取公共属性,可以使用如下方法:

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

      然后,一旦您有了要处理的属性的字符串,您就可以获取集合中的任何对象并使用反射通过其字符串名称调用该属性。

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

      同样可以修改GetCommonPropertiesByName方法中的代码,获取常用的成员、方法、嵌套类型、字段等...

      【讨论】:

      • 因为这是一个快速的模型,我不确定这段代码的性能,但我知道它在相当大的对象数组上运行得相当快。也许更优化的版本会调用 typeList = typeList.Distinct().ToList();在方法中的 foreach 语句之前。
      【解决方案4】:

      嗯,

      您可以在类似于 IComparable 的接口中创建,而是将其称为 IPropertyComparable 之类的名称,然后让实现它的类使用反射来比较它们的属性名称......

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

      我会让你弄清楚其余的。无论如何,它可能会更优雅一点,也许使用 LINQ...

      • 马特

      【讨论】:

        【解决方案5】:

        这是一种从类型列表中获取公共属性集的方法:

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

        这会遍历所有类型中的所有属性,并且仅以出现次数等于类型数的那些属性结束。

        如果您更喜欢紧凑的 LINQ 查询,可以使用以下等效表达式:

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

        【讨论】:

        • 当然,如果您真的想找到类型列表,这不会做,但是从您的帖子中似乎并不太清楚。无论如何,应该可以修改代码以满足您的需要。
        【解决方案6】:

        我使用这样的东西,但托尼的回答可能更好:

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

        【讨论】:

          猜你喜欢
          • 2016-03-30
          • 2019-03-02
          • 1970-01-01
          • 2016-05-14
          • 1970-01-01
          • 2011-08-27
          • 1970-01-01
          • 2014-04-26
          相关资源
          最近更新 更多