Проверять наследование объектов в шкафу объявленным типом - PullRequest
1 голос
/ 13 апреля 2020

Я пытаюсь выбрать правильный способ выбора объектов из контейнера при сравнении типов. В следующем примере есть 2 интерфейса: ISomeInterface, ISomeSubInterface, где ISomeSubInterface наследует от ISomeInterface

Есть также 2 класса: InterfaceClass, SubInterfaceClass, где имена указывают на унаследованный интерфейс.

Кратко: InterfaceClassis ISomeInterface

SubInterfaceClass - это ISomeSubInterface - это ISomeInterface

enter image description here

В методе IsOfType я проверяю соответствие объектов.

Текущее состояние: метод проверяет, принадлежит ли объект в контейнере объявленному типу. При вызове метода с типом ISomeInterface c, если оба класса существуют в контейнере, то оба совпадают, так как оба «являются» ISomeInterface.

Требуемое состояние: метод вернет объект (ы) с самое близкое наследство к ISomeInterface.

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

class Program
{
    static void Main(string[] args)
    {
        _ = new Test();
    }
}

public class Test
{
    private readonly List<ISomeInterface> _objects = new List<ISomeInterface>();

    public Test()
    {
        ISomeInterface obj1 = new InterfaceClass();
        ISomeInterface obj2 = new SubInterfaceClass();

        _objects.Add(obj1);
        _objects.Add(obj2);

        IEnumerable<ISomeSubInterface> retObjs1 = Get<ISomeSubInterface>();
        int count1 = retObjs1.Count(); // 1 As expected

        IEnumerable<ISomeInterface> retObjs2 = Get<ISomeInterface>();
        int count2 = retObjs2.Count(); // 2, wanted 1
    }

    private IEnumerable<T> Get<T>()
        where T : ISomeInterface
    {
        return _objects.Where(IsOfType<T>).Cast<T>();
    }

    private bool IsOfType<T>(ISomeInterface obj)
        where T : ISomeInterface
    {
        return typeof(T).IsAssignableFrom(obj.GetType());
    }
}

internal class SubInterfaceClass: ISomeSubInterface { }
internal class InterfaceClass: ISomeInterface { }
internal interface ISomeSubInterface : ISomeInterface { }
internal interface ISomeInterface { }

Буду признателен за помощь в этом. Спасибо.

Ответы [ 2 ]

1 голос
/ 13 апреля 2020

Зависит от определения «ближайший», это может быть не тривиальной задачей.

Tried-N-True

А сейчас давайте сосредоточимся на вашем пример. Я остановлюсь на этом с другой стороны по сравнению с решением @ OguzOzgul, но все равно буду использовать GetInterfaces.

GetInterfaces , в вашем случае возвращает типы интерфейса в тот же порядок, что и для наследования этих типов:

SubInterfaceClass
0: ISomeSubInterface
1: ISomeInterface

InterfaceClass
0: ISomeInterface

Как вы могли видеть, вы можете найти «ближайший» тип реализации, найдя наименьший возможный индекс, где ISomeInterface находится в. А в вашем случае ISomeInterface находится в позиции 0 в списке интерфейсов InterfaceClass, поэтому вы должны выбрать InterfaceClass.

Это можно сделать с помощью агрегатора:

private T GetClosetImplementationAsPerReflection<T>()
    where T : ISomeInterface
{
    var result = _objects.Where(IsOfType<T>)
        .Aggregate(
            // Default entry
            new { obj = default(T), rank = int.MaxValue },
            // Aggregator
            (currentEntry, o) =>
            {
                // Find all interface types implemented by the instance type
                // Because your class types do not have base type, the order of interfaces
                // appear in the list is same as how the interfaces are inherited.
                // e.g. 
                // SubInterfaceClass: 0->ISomeSubInterface, 1->ISomeInterface
                // InterfaceClass: 0->ISomeInterface
                var entryFound = o.GetType()
                .GetInterfaces()
                .Select((interfaceType, index) => new { interfaceType, index })
                .First(_ => _.interfaceType == typeof(T));

                // Now you could find the type / instance with ISomeInterface
                // at the lowest possible index
                if (entryFound.index < currentEntry.rank)
                {
                    return new { obj = (T)o, rank = entryFound.index };
                }
                else
                {
                    return currentEntry;
                }
            });


    return result.obj;
}

Однако

Вышесказанное является проверенным решением только для вашего тривиального случая. На самом деле такого «самого близкого» определения не существует.

Компилятор компилирует SubInterfaceClass в следующее:

private class SubInterfaceClass : ISomeSubInterface, ISomeInterface
{
}

Так что ISomeInterface, возможно, одинаково близко к SubInterfaceClass по сравнению с InterfaceClass. Хотя вы можете утверждать, что когда ISomeInterface появляется после ISomeSubInterface, тогда ISomeSubInterface ближе, к сожалению, порядок не виден вашему коду. И это не гарантируется GetInterfaces методом либо .

Мое предложение таково: определите, что значит для вас "ближайший", и, в частности, внедрите logi c для своих собственных "ближайших" .

1 голос
/ 13 апреля 2020

Вы можете добиться этого, изменив свой метод IsOfType<T> следующим образом.

Возможно, могут быть лучшие реализации этого оператора linQ (возможно, более эффективный), но он должен дать то, что вам нужно.

private bool IsOfType<T>(ISomeInterface obj)
    where T : ISomeInterface
{
    // The following statement does the following:
    // 1. Check if assignable. If yes;
    // 2. Get all interfaces other than the one we are looking for (T),
    // 3. And see if any of them inherits (T), and if yes, return false.
    // Please see the ! in front of obj.GetType() in the second line
    return
        typeof(T).IsAssignableFrom(obj.GetType()) &&             // Check if assignable first
        !obj.GetType().GetInterfaces()                           // Then, get all interfaces of type
            .Except(new Type[] { typeof(T) })                    // except the interface we are testing against (T)
            .Any((IType) => typeof(T).IsAssignableFrom(IType));  // and see if any of them inherits (T)
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...