C#如何通过反射初始化属性(无setter)

C#如何通过反射初始化属性(无setter),c#,generics,reflection,protocol-buffers,C#,Generics,Reflection,Protocol Buffers,任务: 使用protobuf将对象列表序列化为字节[] 没有反思一切都是好的 proto先生 message MyObject{ int32 id = 1; int32 value = 2; } message MyObjects { repeated MyObject objects = 1; } .cs .cs public static byte[]ToByteArray(List obj)其中T:IMessage其中E:IMessage{ var objects=Acti

任务: 使用protobuf将对象列表序列化为字节[]

没有反思一切都是好的

proto先生

message MyObject{
  int32 id = 1;
  int32 value = 2;
}

message MyObjects {
  repeated MyObject objects = 1;
}
.cs

.cs

public static byte[]ToByteArray(List obj)其中T:IMessage其中E:IMessage{
var objects=Activator.CreateInstance();
objects.GetType().GetProperty(“objects”)?.SetValue(objects,obj);
返回objects.ToByteArray();
} 
问题: 如何使用反射设置值​​对于对象创建期间的属性,就像我在没有反射的情况下所做的那样

如何使用反射编写这个“
newmyobjects{Objects={obj}};
(其中obj:IEnumerable)”

各种结论:

  • 我注意到,填充没有setter的属性只适用于集合,并且只适用于创建对象时
  • 很可能我需要另一种方法来实例化这个类。Activator.CreateInstance()未完成我的任务
    • 当我们这样做时:

      var x = new Thing
      {
          SomeProperty = "x",
          SomeOtherProperty = 1
      }
      
      我们不会在对象创建期间设置值。这相当于:

      var x = new Thing();
      x.SomeProperty = "x";
      x.SomeOtherProperty = 1;
      
      在这两种情况下,属性都是在通过设置属性实例化对象之后设置的。验证这一点的一个简单方法是尝试使用第一个示例中的语法来设置没有setter的属性。它不会编译。您将看到以下错误:

      属性或索引器'Thing.SomeProperty'无法分配给--它是只读的

      换句话说,定义的对象不提供设置
      对象
      属性的方法

      问题是您是否真的需要设置属性。很可能您只需要将项目添加到集合中

      用反射做这件事还是很难看的。我一点也不推荐这个。这是一个粗糙的版本。由于各种原因,它可能在运行时失败

      public static byte[] ToByteArray<T, E>(List<T> itemsToAdd) where T : IMessage where E : IMessage
      {
          // create an instance of the object
          var created = Activator.CreateInstance<E>();
      
          // Find the "Objects" property. It could be null. It could be the wrong type.
          var objectsProperty = typeof(E).GetProperty("Objects"); 
      
          // Get the value of the objects property. Hopefully it's the type you expect it to be.
          var collection = objectsProperty.GetValue(created);
      
          // Get the Add method. This might also be null if the method doesn't exist.
          var addMethod = collection.GetType().GetMethod("Add");
      
          // invoke the Add method for each item in the collection
          foreach(var itemToAdd in itemsToAdd)
          {
              addMethod.Invoke(collection, new object[] { itemToAdd });
          }
          return created.ToByteArray();
      }
      
      我在猜测您的接口是否具有该属性。但如果可能的话,最好使用泛型约束而不是反射。通过这种方式,您的代码在编译时会被检查是否存在大多数可能的错误,而不是运行它并让它崩溃,因为这个或那个属性或方法不存在、是错误的等等

      new()
      约束仅仅意味着
      E
      必须是一个具有默认构造函数的类型,这意味着为了编译它,
      E
      必须是一个可以创建的类型,而无需向构造函数传递任何内容。(如果没有该约束,
      newe()
      将无法编译。)

      如果没有这个约束,即使是
      Activator.CreateInstance
      也可能会失败,因为该类型可能没有默认构造函数。

      解决了这个问题,但我最终使用了一个缩短的解决方案

          private static byte[] ToByteArray<T, E>(IEnumerable<T> obj) where T : IMessage where E : IMessage, new() {
              var objects = new E();
              (objects.GetType().GetProperty("Objects")?.GetValue(objects) as RepeatedField<T>)?.AddRange(obj);
              return objects.ToByteArray();
          }
      
      private static byte[]ToByteArray(IEnumerable obj)其中T:IMessage其中E:IMessage,new(){
      var objects=newe();
      (objects.GetType().GetProperty(“对象”)?.GetValue(对象)作为RepeatedField)?.AddRange(obj);
      返回objects.ToByteArray();
      }
      
      删除protobuf-net,因为这是Google.protobuf,而不是protobuf-net;但是,拥有不可设置的集合是很正常的。只需清除并添加所需内容。属性是两个方法(setter和getter)的语法糖。这些是对您隐藏的实际c#方法,但在所有其他方面都与“正常”方法相同。只读属性只有一个方法(getter)。在这种情况下,您不能调用setter,因为它不存在。您唯一能做的就是编写代码,执行setter在其存在时会执行的操作,例如,尝试设置私有备份字段。这是不推荐的;backing字段的名称是一个不应该依赖的实现细节。此外,并非所有属性都有支持字段。我用类似的方法解决了这个问题,但我可能更喜欢你的。我得到了属性值,并将其转换为RepeatedField,然后调用AddRange(obj)。这个想法是,如果您正在寻找一个您期望存在的特定属性值,那么它意味着您已经知道类型是什么。这正是一般约束的作用。如果您不知道类型是什么,但知道它实现了一个具有特定属性的接口,那么您可以在泛型约束中指定该接口。我不知道protobuf有接口的概念。我将阅读文档。非常感谢你的决定。
      var x = new Thing();
      x.SomeProperty = "x";
      x.SomeOtherProperty = 1;
      
      public static byte[] ToByteArray<T, E>(List<T> itemsToAdd) where T : IMessage where E : IMessage
      {
          // create an instance of the object
          var created = Activator.CreateInstance<E>();
      
          // Find the "Objects" property. It could be null. It could be the wrong type.
          var objectsProperty = typeof(E).GetProperty("Objects"); 
      
          // Get the value of the objects property. Hopefully it's the type you expect it to be.
          var collection = objectsProperty.GetValue(created);
      
          // Get the Add method. This might also be null if the method doesn't exist.
          var addMethod = collection.GetType().GetMethod("Add");
      
          // invoke the Add method for each item in the collection
          foreach(var itemToAdd in itemsToAdd)
          {
              addMethod.Invoke(collection, new object[] { itemToAdd });
          }
          return created.ToByteArray();
      }
      
      public static byte[] ToByteArray<T, E>(List<T> itemsToAdd) 
          where T : IMessage 
          where E : IMessage, new()
      {
          var created = new E();
          foreach (var itemToAdd in itemsToAdd)
          {
              created.Objects.Add(itemToAdd);
          }
      
          // or skip the foreach and just do
          // created.Objects.AddRange(itemToAdd);
      
          return created.ToByteArray();
      }
      
          private static byte[] ToByteArray<T, E>(IEnumerable<T> obj) where T : IMessage where E : IMessage, new() {
              var objects = new E();
              (objects.GetType().GetProperty("Objects")?.GetValue(objects) as RepeatedField<T>)?.AddRange(obj);
              return objects.ToByteArray();
          }