Универсальный способ применения интерфейсов к классам с одинаковыми свойствами - PullRequest
0 голосов
/ 08 мая 2019

У меня есть интерфейс с кучей string свойств:

public interface INameable
{
    string Name { get; }
    string Surname { get; }
}

И класс, который имеет те же свойства, но не реализует интерфейс. Давайте предположим, что у меня нет доступа для изменения класса (то есть, что он реализован во внешней DLL):

public class Person
{
    public string Name { get; set; }
    public string Surname { get; set; }
}

Я написал обобщенный класс, который, учитывая объект внешнего класса, вернет мне экземпляр этого интерфейса со всеми значениями объекта:

public T ExtractInterface<T>(object argument) where T : class
{
    var mock = new Mock<T>();
    foreach (var property in typeof(T).GetProperties())
    {
        var returnValue = argument.GetType().GetProperties().SingleOrDefault(p => string.Equals(p.Name, property.Name, StringComparison.OrdinalIgnoreCase))?.GetValue(argument);

        ParameterExpression value = Expression.Parameter(typeof(T), "value");
        Expression setupProperty = Expression.Property(value, property.Name);
        var func = Expression.Lambda<Func<T, string>>(setupProperty, value);
        mock.Setup(func).Returns((string)returnValue);
    }
    return mock.Object;
}

Это можно использовать так:

var person = new Person { Name = "Joe", Surname = "Blogs" };
var personWithInterface = ExtractInterface<INameable>(person);
personWithInterface.Name.Dump();
personWithInterface.Surname.Dump();

Проблема в том, что он работает только со свойствами string. Может кто-нибудь помочь мне изменить метод, чтобы он работал со свойствами, возвращающими любой тип?

1 Ответ

1 голос
/ 08 мая 2019

Это будет намного проще, даже если вам придется делать это снова и снова.В отличие от использования отражения, вы не получите ошибку времени выполнения, если класс и интерфейс больше не совпадают по совпадению.Он просто не будет компилироваться.

public class PersonWrapper : INameable
{
    private readonly Person _person;
    public PersonWrapper(Person person)
    {
        _person = person ?? throw new ArgumentNullException(nameof(person));
    }

    public string Name => _person.Name;
    public string Surname => _person.Surname;
}

Если вы сделаете что-то «общее» с отражением, это скомпилирует:

var list = new List<string>();
INameable person = ExtractInterface<INameable>(list);

Если у нас нет абсолютно никакого выбора, ничего, что «обманывает»«компилятор, чтобы мы могли компилировать вещи, которые не должны компилироваться, - это рецепт неприятностей.Он забирает один из самых мощных инструментов, которые мы можем использовать для предотвращения ошибок во время выполнения.


По сути, это тот же подход, который используется для "адаптации" HttpContext к HttpContextBase, HttpRequest к * 1013.* и т. д., за исключением того, что это абстрактные классы вместо интерфейсов.

Первоначальные реализации не реализовывали интерфейс и не наследовали от абстрактного класса, но позже стало очевидно, что было бы полезно, если бы были абстракции (использующие термин свободно) для этих классов.

Таким образом, они создали новые абстрактные классы с точно такими же свойствами, как и у исходных классов, так же, как вы создали новые интерфейсы для некоторых существующих классов.

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

...