5

我仍在研究我的通用日志记录组件,它应该处理任何类型的 anobject并将其转换(“序列化”它)为字符串。

到目前为止工作得很好 - 除了一个要求:我应该能够识别给定对象的基本类型(如果存在),并且还单独列出该基本类型的属性。

基本上,我有一个类似这样的方法:

public string LogObject(object myObject)

现在我想检查这个对象的类型是否有一个基类,如果有的话 - 获取我的对象的属性,“向下转换”到它的基类。

基本类型的检查并不难:

Type baseType = myObject.GetType().BaseType;

但是我现在如何向下转换myObject为类型的对象baseType

我尝试了几件事,例如:

object baseObject = Convert.ChangeType(myObject, baseType);

但这需要我的对象类型来实现IConvertible,我不可能从我的所有对象中要求...

还有什么办法吗?

那些当然是行不通的......

object baseObject = myObject as baseType;
object baseObject = (baseType)myObject;

还有其他我没有想到的方法吗?

更新:我已经考虑过这样做

  • 获取myObjectinto的所有属性allProperties
  • myObject将类型上声明的那些属性抓取到declaredProperties( BindingFlag.Declared)
  • baseProperties通过减去declaredPropertiesfrom得到allProperties

但这在反思中似乎有点过分——不确定这是否会表现得体面....

4

6 回答 6

4

使用 BindingFlags.DeclaredOnly 仅获取在特定类型上声明的成员。此外,即使您不使用此标志,每个返回的 ProperyInfo 都有一个 DeclaredType 属性,该属性列出了声明该属性的类型。

我能想到使用DeclaredOnly 的唯一原因是如果您想更好地控制结果集,例如过滤掉子类中已被覆盖的虚拟属性。

PS:使用像Fasterflect这样的库可以使这样的任务变得轻而易举;)

于 2013-05-14T21:28:54.297 回答
2

解决方案:

如果有人感兴趣 - 根据 Oded 和 Morten 的评论,这是我最终使用的解决方案:

// get all the properties of "myObject"
List<PropertyInfo> propertyInfoList = new List<PropertyInfo>(myObject.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public));

// get the object's type and its base type
Type objectType = objectToLog.GetType();
Type baseType = objectToLog.GetType().BaseType;

// if a baseType exists ...
if (baseType != null)
{
    // get the list of properties that are *not* defined directly in "myObject" - 
    // those are all the properties defined in the immediate and possible other base types
    List<PropertyInfo> baseProperties = propertyInfoList.Where(x => x.DeclaringType != objectType).ToList();

    // process those base properties

    // after processing, remove the base properties from the list of "all" properties to get just those
    // properties that are defined directly on the "myObject" type

    List<PropertyInfo> declaredProperties = propertyInfoList.Except(baseProperties);        
}
于 2013-05-14T21:30:34.850 回答
0

您不会列出对象的属性,而是列出类型的属性。这是因为属性是从类型描述符中读取的,而不是从对象中读取的。您可以从此链接了解更多信息。因此,没有必要(在 C# 中也没有办法;但可以在 C++ 中完成)将对象强制转换或转换为其基本类型——它已经是它的基本类型。

在相关的说明中,向下转换意味着从基类型转换为派生类型,这是一个棘手的操作,因为它可能在运行时失败,因为在编译时不知道手头的对象是否真的是派生类型。

无论如何,您的问题的解决方案是这样的:

Type baseType = myObject.GetType().BaseType;
PropertyInfo[] props = null;
if (baseType != null)
    props = baseType.GetProperties();
else
    props = new PropertyInfo[0]; // Just ensure that props is non-null

在另一个相关说明中,这是一个类库,它完全符合您的要求:link

于 2013-05-14T21:35:40.073 回答
0

您可以为所有对象编写 Serialize 方法或更好地覆盖 ToString(),其中每个对象调用基类方法并将其自己的信息附加到其基类返回的内容中。

这可能需要一些额外的编码,为您的每个类编写一个额外的方法,并且不考虑您没有编写的类,但是如果您希望远离反射,这意味着您必须灵活.

最后,为什么要排除反射,除非您要进行一些荒谬的日志记录,否则它不应该让您放慢速度并记住“过早的优化是万恶之源”

于 2013-05-14T21:44:15.810 回答
0

绑定标志是你的朋友。给定这样的类层次结构:

class Foo
{
    public string FooProperty { get ; set ; }
    public virtual void FooMethod1() { return ; }
    public virtual void FooMethod2() { return ; }
}
class Bar : Foo
{
    public string FooProperty { get ; set ; }
    public override void FooMethod1() { return ; }
    public void BarMethod1() { return ; }
}

像这样的代码:

for ( Type t = typeof( Bar ) ; t != null ; t = t.BaseType )
{
  MemberInfo[] members = t.GetMembers( BindingFlags.DeclaredOnly|BindingFlags.Instance|BindingFlags.Public ) ;

  Console.WriteLine() ;
  Console.WriteLine( "Type {0}:" , t.FullName ) ;

  // enumerate the methods directly implemented by the type
  Console.WriteLine( "* Methods:" ) ;
  int memberCount = 0 ;
  foreach ( MethodInfo method in members.Where( x => x.MemberType == MemberTypes.Method ).Select( x => (MethodInfo)x ) )
  {
    ++memberCount ;
    Console.WriteLine("  - {0}( {1} )" , method.Name , string.Join(" , " , method.GetParameters().Select(p=>p.ParameterType.Name)) ) ;
  }
  if ( memberCount == 0 ) {  Console.WriteLine("  n/a" ) ; }

  // enumerate the properties directly implemented by the type
  Console.WriteLine( "* Properties:" ) ;
  int propertyCount = 0 ;
  foreach ( PropertyInfo property in members.Where( x => x.MemberType == MemberTypes.Property ).Select( x => (PropertyInfo)x ) )
  {
    ++propertyCount ;
    Console.WriteLine("  - {0}: {1}" , property.Name , property.PropertyType.FullName ) ;
  }
  if ( propertyCount == 0 ) {  Console.WriteLine("  n/a" ) ; }

}

产生这个:

Type ConsoleApplication11.Bar:
* Methods:
  - get_FooProperty(  )
  - set_FooProperty( String )
  - FooMethod1(  )
  - BarMethod1(  )
* Properties:
  - FooProperty: System.String

Type ConsoleApplication11.Foo:
* Methods:
  - get_FooProperty(  )
  - set_FooProperty( String )
  - FooMethod1(  )
  - FooMethod2(  )
* Properties:
  - FooProperty: System.String

Type System.Object:
* Methods:
  - ToString(  )
  - Equals( Object )
  - GetHashCode(  )
  - GetType(  )
* Properties:
  n/a

不过,有趣的是,这些属性既作为它们的属性公开,也作为实现该属性的方法公开。想知道如何从方法列表中过滤掉属性方法吗?

于 2013-05-14T21:46:44.073 回答
-1

您的类中的所有属性也会列出所有基类,

如果你想要一个只有基本类型的实例,你必须将它克隆到一个新实例。

PropertyInfo[] properties = typeof(basetype).GetProperties();
basetype b = new basetype();
foreach (PropertyInfo pi in properties)
{
     if (pi.GetSetMethod() != null)
     {
        pi.SetValue(b, pi.GetValue(theobject,null), null);
     }
}

只要有可用的 set 方法,值就会从派生对象(称为 theobejct)复制到称为 b 的新对象。

现在这个类也可能有一个基类型,等等。使用泛型,您可以创建一个方法来处理所有可能的类的此功能。

请注意,如果类 A 具有属性 X 并且 B 派生自 A 和 C 派生自 B,那么您将获得属性 X 的值三倍。

于 2013-05-14T21:28:39.433 回答