Могу ли я переопределить ToString () для классов в открытом API? - PullRequest
4 голосов
/ 18 ноября 2010

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

Изначально я думал сделать что-то вроде приведенного ниже кода с длинным оператором Switch.Хотя это, скорее всего, станет неуправляемо длинным.

public string GetAPIObjectDescrition(object obj)
{
     Type t = obj.GetType();

     Switch(t)
     { 
         Case typeof(SomeAPIType):
             SomeAPIType x = (SomeAPIType)obj;
             return  x.SomeProperty;             


         Case typeof(SomeOtherAPIType):
             SomeOtherAPITypex = (SomeOtherAPIType)obj;
             return  x.SomeOtherProperty;

         default:
             return x.ToString();
     }
} 

Далее я попытался использовать методы расширения (см. Код ниже).CustomObjectDescription () работал как положено, но когда я пытался вызвать ToString (), он просто возвращает результаты ToString () по умолчанию.Я никогда раньше не использовал методы расширения, поэтому я мог быть совершенно не в своей тарелке, думая, что что-то подобное даже возможно.

Я не могу гарантировать, что будет расширение CustomObjectDescription () для каждого Типа, встречающегося в APIпоэтому, если я выберу этот маршрут, мне придется каждый раз использовать отражение, чтобы проверить, имеет ли текущий объект расширение GetObjectDescription ().Я хотел бы избегать использования рефлексии, если это вообще возможно.

public static class APIObjectDescriptionExtensions
{
    public static string ToString(this APIObject element)
    {
        return "ElementName = " + element.Name + " ElementID =" + element.Id.IntegerValue.ToString();
    }

    public static string CustomObjectDescription(this APIObject element)
    {
        return "ElementName = " + element.Name + " ElementID =" + element.Id.IntegerValue.ToString();
    }
}

У кого-нибудь есть какие-либо другие предложения о том, как мне подходить к этой проблеме?Я бы предпочел решение, в котором код для каждого типа API не зависит друг от друга (нет гигантского оператора Switch).

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

Я думаю, что может быть лучшее решениеэто включает создание пользовательских TypeConverters или, возможно, переопределение / расширение System.Convert.ToString ()?


Обновление

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

    public static string GetDataDescription(object o)
    {
        //get the type of the input object
        Type objectType = o.GetType();

        //check to see if a description extension method is defined
        System.Reflection.MethodInfo extensionMethod = objectType.GetMethod("MyDescriptionExtensionMethod");

        if (extensionMethod != null)
        {
            //if a description extension method was found returt the result
            return (string)extensionMethod.Invoke(o, new object[] { });
        }
        else
        {
            //otherwise just use ToString();
            return o.ToString();
        }
    }

Этот код выше не работает, хотя методы расширения не найдены GetMethod () .

Ответы [ 5 ]

2 голосов
/ 18 ноября 2010

Вы можете предоставить оболочку для каждого из классов, подобных этому:

    public class SomeAPITypeWrapper : SomeAPIType
    {
        public override string ToString()
        {
            return SomeProperty;
        }
    }

    public class SomeOtherAPITypeWrapper : SomeOtherAPIType
    {
        public override string ToString()
        {
            return SomeOtherProperty;
        }
    }

Это, безусловно, позволяет использовать базовые классы / подклассы в соответствии с вашим вопросом. Он также поддерживает его в чистоте и в самой вашей объектной модели, а не в операторе switch или вспомогательном классе.

0 голосов
/ 13 июля 2014

Вы можете отложить все действия до отдельного вопроса вашего приложения. StatePrinter (https://github.com/kbilsted/StatePrinter) - это один из таких API, где вы можете использовать значения по умолчанию или настроить в зависимости от типов для печати. ​​

var car = new Car(new SteeringWheel(new FoamGrip("Plastic")));
car.Brand = "Toyota";

затем распечатайте

StatePrinter printer = new StatePrinter();
Console.WriteLine(printer.PrintObject(car));

и вы получите следующий вывод

new Car() {
    StereoAmplifiers = null
    steeringWheel = new SteeringWheel()
    {
        Size = 3
        Grip = new FoamGrip()
        {
            Material = ""Plastic""
        }
        Weight = 525
    }
    Brand = ""Toyota"" }

и с помощью абстракции IValueConverter вы можете определить тип принтера, а с помощью FieldHarvester вы можете указать, какие поля должны быть включены в строку.

0 голосов
/ 18 ноября 2010

Предлагаю сделать Dictionary<Type,Converter<object,string>>. Затем вы можете найти пользовательский стрингизер, и если ничего не найдено, позвоните ToString.

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

Обратите внимание, что вы можете создать "открытый делегат" для Object.ToString(), который соответствует контракту Converter<object,string>, и использовать его по умолчанию, даже сохранить его в словаре, вместо того, чтобы в специальном случае вызывать ToString .

0 голосов
/ 18 ноября 2010

Если данный вызов метода может быть разрешен методом экземпляра и методом расширения, метод экземпляра получает преимущество. Таким образом, методы расширения должны быть названы так, чтобы они не имели тех же имен, что и методы в расширенном типе.

Из приведенного выше кода кажется, что вы не контролируете источник APIObject и его производные. Таким образом, вы можете выбрать ' Ввести внешний метод ' и ' Ввести локальное расширение '

  • Я бы попробовал сторонний метод (который похож на методы расширения C #) ... хотя я не уверен, зачем вам нужно отражение. Если метод расширения не существует, это будет ошибка во время компиляции. Как вы потребляете этот метод?
  • Наконец, операторы switch не так уж и плохи ... если только они не очень длинные / не требуют частых изменений / дублируются в разных местах.
0 голосов
/ 18 ноября 2010

Вы пытались использовать другое имя, отличное от ToString () в вашем классе расширения?Я не совсем уверен насчет методов расширения, но я предполагаю, что base.ToString вызывался вместо вашегоВозможно, создание метода расширения ToDescription () даст лучшие результаты.

...