Как мне определить поля списка с помощью отражения и добавить новые объекты в этот список?

#c# #reflection

#c# #отражение

Вопрос:

У меня есть два объекта, которые имеют в основном свойства с одинаковым типом и именем, и у меня есть метод, который присваивает значения из объекта A соответствующим свойствам в объекте B. Я пишу модульный тест, который должен вызывать этот метод, а затем утверждать, что для каждого свойства в объекте A, которое имеет соответствующее свойство в объекте B, значение было скопировано.

Моя идея для достижения этой цели состоит в том, чтобы использовать отражение для перечисления всех свойств в объекте A, а затем присвоить каждому свойству случайное значение. Затем я вызываю свой метод «копировать значения», чтобы скопировать значения в объект B, а затем использую отражение, чтобы перечислить поля для каждого объекта и убедиться, что поля с совпадающими именами имеют одинаковое значение.

Вот мой код для присвоения случайных значений объекту A

 var objectA = new ObjectA();
var random = new Random();
var fields = typeof(ObjectA).GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var field in fields)
{
    if (field.CanWrite)
    {
        if (field.PropertyType == typeof(bool) || field.PropertyType == typeof(bool?))
        {
            field.SetValue(objectA, Convert.ToBoolean(random.Next(2)));
        }
        if (field.PropertyType == typeof(decimal) || field.PropertyType == typeof(decimal?))
        {
            field.SetValue(objectA, Convert.ToDecimal(random.Next()));
        }
        if (field.PropertyType == typeof(string))
        {
            field.SetValue(objectA, random.Next().ToString());
        }
        // similar code for other data types
    }
}
  

Затем я вызываю свой метод для копирования значений:

 ObjectB objectB = ObjectB.FromObjectA(objectA);
  

Затем я вызываю этот метод для сравнения значений двух объектов:

 public static void AssertMatchingFieldAreEqual<T1, T2>(T1 a, T2 b)
{
    foreach (var fieldA in typeof(T1).GetProperties(BindingFlags.Instance | BindingFlags.Public))
    {
        var fieldInfoB = typeof(T2).GetProperty(fieldA.Name);
        if (fieldInfoB != null)
        {
            var propertyA = typeof(T1).GetProperty(fieldA.Name);
            var propertyB = typeof(T2).GetProperty(fieldA.Name);

            // Adding field names to make error messages for failed Assert calls list the field name
            var valueA = $"{propertyA.Name}: {propertyA.GetValue(a)}";
            var valueB = $"{propertyB.Name}: {propertyB.GetValue(b)}";

            Assert.AreEqual(valueA, valueB);
        }
    }
}
  

Это работает для базовых типов данных. Моя проблема в том, что у меня есть несколько полей, которые равны List s, и я хотел бы заполнить их случайным количеством объектов их типа, а затем утверждать, что при копировании полей в каждом списке одинаковое количество объектов.

Мои два вопроса:

  1. Когда я присваиваю значения, как мне проверить, является ли свойство a List , не зная типа элемента в списке? Я пробовал if (field.PropertyType == typeof(List<object>) , но это не работает.

  2. Как мне создать новый объект типа T add и добавить его в мой список, если мой тип свойства — список?

Или, в качестве альтернативы, если есть лучший способ проверить, что мой метод «копировать значения» копирует все поля с одинаковыми именами, какой способ лучше?

Ответ №1:

Прямой ответ на ваш вопрос заключается в том, что вам нужно будет включить общее тестирование в свое отражение.

     if(field.PropertyType.GetGenericTypeDefinition() == typeof(List<>)){
        var typeOfThingInside = field.PropertyType.GetGenericArguments()[0];
        // ...
    }
  

Лучший способ — использовать автофиксацию для создания ваших объектов и использовать Fluent Assertions, чтобы проверить, что все скопировано.

 void Main()
{
    // Arrange
    var fixture = new Fixture();
    var a = fixture.Create<ObjectA>();
    
    // Act
    var b = ObjectB.FromObjectA(a);
    
    // Assert
    b.Should().BeEquivalentTo(a, options => options.ExcludingMissingMembers());
}

public class ObjectA {
    public int A {get;set;}
    public string B {get;set;}
    public List<string> C {get;set;}
    public decimal Z {get;set;}
}
public class ObjectB
{
    public int A { get; set; }
    public string B { get; set; }
    public List<string> C { get; set; }
    public decimal Y { get; set; }
    
    public static ObjectB FromObjectA(ObjectA a) => JsonConvert.DeserializeObject<ObjectB>(JsonConvert.SerializeObject(a));
}
  

Комментарии:

1. Это здорово, спасибо. Я не знал об автофиксации и плавных утверждениях. Они оба делают это намного проще.