Хорошая практика для создания методов расширения, применимых к System.Object?

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

public static string SafeToString(this Object o) {
    if (o == null || o is System.DBNull)
        return "";
    else {
        if (o is string)
            return (string)o;
        else
            return "";
    }
}

public static int SafeToInt(this Object o) {
    if (o == null || o is System.DBNull)
        return 0;
    else {
        if (o.IsNumeric())
            return Convert.ToInt32(o);
        else
            return 0;
    }
}
//same for double.. etc

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

Я хотел бы знать, хороший ли это стиль, приемлемый стиль или плохой стиль. Меня это беспокоит, так как это как бы «загрязняет» объектный класс.

Заранее спасибо и С наилучшими пожеланиями :)

Христианин

P.S. Я специально не пометил это как «субъективное».


person Christian    schedule 10.03.2010    source источник


Ответы (7)


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

Однако методы расширения для объектов данных для работы со значениями Null - это очень хорошее применение методов расширения. Подумайте о том, чтобы передать их вам OleDbDataReader. У меня есть общий метод расширения под названием ValueOrDefault that. . . ну я тебе просто покажу:

<Extension()> _
Public Function ValueOrDefault(Of T)(ByVal r As DataRow, ByVal fieldName As String) As T
    If r.IsNull(fieldName) Then
        If GetType(T) Is GetType(String) Then
            Return CType(CType("", Object), T)
        Else
            Return Nothing
        End If
    Else
        Return CType(r.Item(fieldName), T)
    End If
End Function

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

Помещение методов расширения в отдельное пространство имен лучше, чем ничего (это совершенно допустимое использование пространств имен; Linq использует это), но в этом нет необходимости. Чтобы применить эти методы к различным объектам базы данных, примените методы расширения к IDataRecord.

person Patrick Karcher    schedule 10.03.2010
comment
Я нашел все ответы полезными, но этот был самым полезным, поскольку он не только указывал на проблемы, но и на несколько решений :) Большое спасибо! - person Christian; 10.03.2010
comment
@Patrick Karcher - Я думаю, что ваш ответ довольно хорош (я проголосовал за него), но не хватает одного ключевого элемента. Вы заявляете о сильном убеждении, что это неправильно, но не объясняете почему это неправильно. Интеллектуальный беспорядок - это правда, но он недостаточно силен, чтобы соответствовать силе ваших чувств. Использовано или зависело неправильно, неопределенно, и вы не можете установить, как наличие метода на объекте может вызвать такую ​​ошибку. Итак ... это просто догма с вашей стороны, или у вас действительно есть веские причины для того, что вы чувствуете? - person Charlie Flowers; 17.04.2013

Отрывок из книги «Рекомендации по проектированию каркаса».

Избегайте определения методов расширения в System.Object, за исключением случаев крайней необходимости. При этом имейте в виду, что пользователи VB не смогут использовать определенные таким образом методы расширения и, как таковые, они не смогут воспользоваться преимуществами удобства использования / синтаксиса, которые приходят с методами расширения.

Это связано с тем, что в VB объявление переменной как объекта приводит к тому, что все вызовы методов для нее должны быть связаны с поздним связыванием, в то время как привязки к методам расширения определяются во время компиляции (ранняя привязка). Например:

public static class SomeExtensions{
     static void Foo(this object o){…} } … Object o = … o.Foo(); 

В этом примере вызов Foo в VB завершится ошибкой. Вместо этого синтаксис VB должен быть просто: SomeExtensions.Foo (o)
Обратите внимание, что рекомендации применяются к другим языкам, где присутствует такое же поведение привязки или где методы расширения не поддерживаются.

person Daniel Melo    schedule 10.03.2010
comment
Ты шустрый! Я все еще искал эту цитату. +1 - person Steven; 10.03.2010
comment
Но жалоба на VB совершенно неуместна, поскольку применяется только в том случае, если переменная объявлена ​​как объект типа. Но аргумент Кристиана в пользу расширения Object, похоже, заключается в применении ко множеству различных несвязанных типов переменных, а не в применении к типизированным переменным Object. - person Ben Voigt; 10.03.2010

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

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


Одна вещь, которая лично меня беспокоит в распространении методов расширения повсюду, - это то, что мой процессор (мой мозг) обучен находить возможные NullReferenceExceptions. Поскольку метод расширения выглядит как метод экземпляра, мой мозг часто получает PossibleUseOfNullObject прерывание от анализатора исходного кода при чтении такого кода. В этом случае я должен проанализировать, может ли действительно произойти NullReferenceException или нет. Это значительно усложняет чтение кода, потому что меня чаще прерывают.

По этой причине я очень консервативен в использовании методов расширения. Но это не значит, что я не считаю их полезными. Конечно нет! Я даже написал библиотеку для проверки предварительных условий, в которой широко используются методы расширения. Я даже изначально определил методы расширения на System.Object, когда начал писать эта библиотека. Однако, поскольку это многоразовая библиотека, которую используют разработчики VB, я решил удалить эти методы расширения на System.Object.

person Steven    schedule 10.03.2010

Загрязнение методов расширения System.Object может быть довольно раздражающим в общем случае, но вы можете поместить эти методы расширения в отдельное пространство имен, чтобы разработчики должны были активно соглашаться на использование этих методов.

Если вы объедините это с кодом, который следует принципу единой ответственности, вам нужно будет импортировать это пространство имен только в относительно небольшое количество классов.

При таких обстоятельствах такие методы расширения могут быть приемлемыми.

person Mark Seemann    schedule 10.03.2010
comment
Я думаю, что размещение их в другом пространстве имен лечит симптом больше, чем болезнь. Это слишком высоко в цепочке наследования. Это будет мешать и сбивать с толку. - person Patrick Karcher; 10.03.2010

Возможно, подумайте о добавлении методов расширения в интерфейс IDataRecord? (который реализует ваш OleDbDataReader)

public static class DataRecordExtensions
{
    public static string GetStringSafely(this IDataRecord record, int index)
    {
        if (record.IsDBNull(index))
            return string.Empty;

         return record.GetString(index);            
    }

    public static Guid GetGuidSafely(this IDataRecord record, int index)
    {
        if (record.IsDBNull(index))
            return default(Guid);

        return record.GetGuid(index);
    }

    public static DateTime GetDateTimeSafely(this IDataRecord record, int index)
    {
        if (record.IsDBNull(index))
            return default(DateTime);

        return record.GetDateTime(index);
    }
}
person David Masters    schedule 10.03.2010
comment
+1 Я бы держался подальше от грязного класса System.Object. Как и в этом примере, я бы сосредоточился на причине, по которой вы добавляете метод расширения, то есть, чтобы избавить вас от горя при работе с данными базы данных. О, и вы сможете сделать свой код более производительным; снова, поскольку этот пример предполагает, что вам нужно только выполнить проверку IsDBNull, а не пытаться кодировать для каждого сценария ... - person Chris Moutray; 10.03.2010

Помимо уже упомянутых причин, следует иметь в виду, что методы расширения на System.Object не поддерживаются в VB (пока переменная статически типизирована как System.Object, а если переменная не набрана статически как Object, вам лучше расширить другую более конкретный тип).

Причина этого ограничения - обратная совместимость. См. этот вопрос для получения дополнительной информации.

person Dirk Vollmar    schedule 10.03.2010
comment
в случае плаката метод должен применяться к широкому спектру переменных, ни одна из которых не типизирована как объект, но не имеет другого общего класса-предка. - person Ben Voigt; 10.03.2010

Я знаю, что это не лучшая практика, но в свои проекты мне нравится включать это расширение для преобразования типов данных, оно мне намного проще, чем класс Convert ..

public static T ChangeType<T>(this object obj) where T : new()
{
    try
    {
        Type type = typeof(T);
        return (T)Convert.ChangeType(obj, type);
    }
    catch
    {
        return new T();
    }
}

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

Работает вот так ...

int i = "32".ChangeType<int>();
bool success = "true".ChangeType<bool>();
person jaekie    schedule 31.03.2010