универсальная функция с ограничением "имеет свойство X"? - PullRequest
16 голосов
/ 20 августа 2009

У меня есть стороннее приложение с закрытым исходным кодом, которое экспортирует интерфейс COM, который я использую в своем приложении C # .NET через Interop. Этот COM-интерфейс экспортирует многие объекты, которые отображаются как System.Object, пока я не приведу их к соответствующему типу интерфейса. Я хочу назначить свойство всех этих объектов. Таким образом:

foreach (object x in BigComInterface.Chickens)
{
    (x as Chicken).attribute = value;
}
foreach (object x in BigComInterface.Ducks)
{
    (x as Duck).attribute = value;
}

Но присвоение свойства может (по определенным для приложения причинам, которые неизбежны) генерировать исключения, из которых я хочу восстановить, поэтому я действительно хочу попробовать / поймать каждую из них. Таким образом:

foreach (object x in BigComInterface.Chickens)
{
    try
    {
        (x as Chicken).attribute = value;
    }
    catch (Exception ex)
    {
        // handle...
    }
}
foreach (object x in BigComInterface.Ducks)
{
    try
    {
        (x as Duck).attribute = value;
    }
    catch (Exception ex)
    {
        // handle...
    }
}

Очевидно, это было бы намного чище:

foreach (object x in BigComInterface.Chickens)
{
    SetAttribute<Chicken>(x as Chicken, value);
}
foreach (object x in BigComInterface.Ducks)
{
    SetAttribute<Duck>(x as Duck, value);
}

void SetAttribute<T>(T x, System.Object value)
{
    try
    {
        x.attribute = value;
    }
    catch
    {
        // handle...
    }
}

Видишь проблему? Мое значение x может быть любого типа, поэтому компилятор не может разрешить .attribute . Цыпленок и Утка не находятся в каком-либо дереве наследования и не имеют общего интерфейса, который имеет .attribute . Если бы они это сделали, я мог бы установить ограничение для этого интерфейса на T . Но так как класс с закрытым исходным кодом, для меня это невозможно.

В моей фантазии я хочу что-то вроде ограничения, требующего, чтобы аргумент имел свойство .attribute независимо от того, реализует ли он данный интерфейс. Для остроумия,

void SetAttribute<T>(T x, System.Object value) where T:hasproperty(attribute)

Я не уверен, что делать дальше, кроме как вырезать / вставить этот маленький блок try / catch для каждого из Цыпленка, Утки, Коровы, Овцы и т. Д.

У меня такой вопрос: как можно решить эту проблему, когда нужно вызвать конкретное свойство объекта, когда интерфейс, реализующий это свойство, не может быть известен во время компиляции?

Ответы [ 4 ]

6 голосов
/ 26 августа 2009

Ну, в зависимости от того, насколько огромен ваш код обработки исключений (и если я не ошибаюсь, это может быть так), то вам может помочь следующий трюк:

class Chicken
{
    public string attribute { get; set; }
}

class Duck
{
    public string attribute { get; set; }
}

interface IHasAttribute
{
    string attribute { get; set; }
}

class ChickenWrapper : IHasAttribute
{
    private Chicken chick = null;
    public string attribute
    {
        get { return chick.attribute; }
        set { chick.attribute = value; }
    }
    public ChickenWrapper(object chick)
    {
        this.chick = chick as Chicken;
    }
}

class DuckWrapper : IHasAttribute
{
    private Duck duck = null;
    public string attribute
    {
        get { return duck.attribute; }
        set { duck.attribute = value; }
    }
    public DuckWrapper(object duck)
    {
        this.duck = duck as Duck;
    }
}

void SetAttribute<T>(T x, string value) where T : IHasAttribute
{
    try
    {
        x.attribute = value;
    }
    catch
    {
        // handle...
    }
}
4 голосов
/ 20 августа 2009

К сожалению, это сложно в настоящее время. В C # 4 динамический тип может немного помочь с этим. COM-взаимодействие - одно из тех мест, которые действительно динамичны.

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

Вы можете использовать отражение, чтобы найти свойство «attribute» и установить его значение во время выполнения.

3 голосов
/ 27 декабря 2016

Чтобы не нарушать принцип СУХОГО, вы можете использовать отражение. Если вы действительно хотите использовать дженерики, вы можете использовать класс-оболочку для каждого типа стороннего объекта и сделать так, чтобы обертка реализовала интерфейс, который можно использовать для ограничения аргумента универсального типа.

Пример того, как это можно сделать с помощью отражения. Код не проверен, хотя.

    void SetAttribute(object chickenDuckOrWhatever, string attributeName, string value)
    {
        var typeOfObject = chickenDuckOrWhatever.GetType();
        var property = typeOfObject.GetProperty(attributeName);
        if (property != null)
        {
            property.SetValue(chickenDuckOrWhatever, value);
            return;
        }

        //No property with this name was found, fall back to field
        var field = typeOfObject.GetField(attributeName);
        if (field == null) throw new Exception("No property or field was found on type '" + typeOfObject.FullName + "' by the name of '" + attributeName + "'.");
        field.SetValue(chickenDuckOrWhatever, value);
    }

Если вы хотите ускорить выполнение кода, вы можете кэшировать FieldInfo и PropertyInfo для каждого типа chickenDuckOrWhatever и сначала просмотреть словарь, прежде чем отражать.

СОВЕТ: Чтобы не вводить код attributeName в виде строки, вы можете использовать функцию C # 6 nameof . Например, nameof (Chicken.AttributeName).

2 голосов
/ 20 августа 2009

К сожалению, единственный способ сделать это - ограничить параметр типа интерфейсом, который определяет это свойство и применяется ко всем типам.

Поскольку у вас нет источника, это будет невозможно реализовать, и поэтому вам придется использовать какой-то обходной путь. C # имеет статическую типизацию и поэтому не поддерживает ту типизацию, которую вы хотите использовать здесь. В ближайшее время (в C # 4) лучше всего будет ввести объект как dynamic и разрешить вызовы свойств во время выполнения (обратите внимание, что этот подход также не будет универсальным, поскольку вы не можете ограничить параметр универсального типа как dynamic ).

...