Методы расширения и методы экземпляра против статического класса

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

  • Вызов метода экземпляра
  • Использование статического класса в POCO
  • Создание метода расширения

Пример:

public class MyPoint
{
    public double x { get; set; }
    public double y { get; set; }

    public double? DistanceFrom(MyPoint p)
    {
        if (p != null)
        {
            return  Math.Sqrt(Math.Pow(this.x - p.x, 2) + Math.Pow(this.y - p.y, 2));
        }
        return null;
    }
}

Если бы вы могли достичь желаемого результата, просто поместив метод в определение класса, почему POCO в сочетании с статическим вспомогательным классом или методом расширения предпочтительнее?

Ответ 1

Вы спросили: "Если бы вы могли достичь желаемого результата, просто поместив метод в определение класса, почему бы POCO в сочетании с статическим вспомогательным классом или методом расширения предпочтительнее?"

Ответ заключается в том, что это зависит от ситуации, и если рассматриваемые методы напрямую связаны с основной задачей вашего класса (см. принцип единой ответственности).

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

1. Методы экземпляров

//This all makes sense as instance methods because you're 
//encapsulating logic MyPoint is concerned with.
public class MyPoint
{
    public double x { get; set; }
    public double y { get; set; }

    public double? DistanceFrom(MyPoint p)
    {
        if (p != null)
            return  Math.Sqrt(Math.Pow(this.x - p.x, 2) + Math.Pow(this.y - p.y, 2));            
        return null;
    }
}

2. Методы статического класса - пример регистрации ошибок.

    //Your class doesn't directly concern itself with logging implmentation;
    //that something that is better left to a separate class, perhaps
    //a "Logger" utility class with static methods that are available to your class.
    public double? DistanceFrom(MyPoint p)
    {
        try
        {
            if (p != null)
                return  Math.Sqrt(Math.Pow(this.x - p.x, 2) + Math.Pow(this.y - p.y, 2));            
            return null;
        }
        catch(Exception ex)
        {
             //**** Static helper class that can be called from other classes ****
             Logger.LogError(ex);

             //NOTE: Logger might encapsulate other logging methods like...
             //Logger.LogInformation(string s)
             //...so an extension method would be less natural, since Logger
             //doesn't relate to a specific base type that you can create an
             //extension method for.
        }
}

3. Методы расширения. Пример XML-сериализации.

//Maybe you want to make it so that any object can XML serialize itself
//using an easy-to-use, shared syntax.
//Your MyPoint class isn't directly concerned about XML serialization,
//so it doesn't make sense to implement this as an instance method but
//MyPoint can pick up this capability from this extension method.
public static class XmlSerialization
{
    public static string ToXml(this object valueToSerialize)
    {
        var serializer = new XmlSerializer(valueToSerialize.GetType());
        var sb = new StringBuilder();
        using (var writer = new StringWriter(sb))
            serializer.Serialize(writer, valueToSerialize);

        return sb.ToString();
    }
}

//example usage
var point = new MyPoint();
var pointXml = point.ToXml(); //<- from the extension method

Правило большого пальца:

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

Ответ 2

Экземпляр/статические методы

Доступные члены: public, protected, private (невозможно получить доступ, если унаследовано)

Определение: тот же класс/структура/интерфейс (может быть разделен на файлы с ключевым словом partial)

Вызывается как: object.Method()

В этом контексте я имею в виду, что Статические методы - это методы, определенные в классе, которым они управляют. То есть они определяются рядом с другими объектами класса. (Статические методы, определенные в классе MyPoint в вашем примере кода.)

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

У экземпляров есть доступ к всем private, protected и public членам класса. Как и методы static.

В большинстве случаев, если у вас есть большое количество методов и/или свойств для добавления, или они значительно изменяют работу объекта, вы должны наследовать оригинальный объект (если возможно). Это дает вам доступ ко всем public и protected членам class/struct/interface.

Методы класса статического помощника

Доступные члены: public

Определение: любое пространство классов/имен

Вызывается как: HelperClass.Method(object)

Методы класса Static Helper Я подразумеваю, что фактическое определение методов static, упомянутых в этом разделе, не в определении фактического класса. (Например, класс, подобный MyPointHelpers или аналогичный, используя пример вашего кода.)

Методы класса Static Helper только имеют доступ к членам public объекта (подобно методам расширения, я написал этот раздел после раздела метода расширения).

Классы статического помощника и методы расширения тесно связаны и во многих случаях одинаковы. Поэтому я оставлю преимущества для них в разделе "Методы расширения".

Методы расширения

Доступные члены: public

Определение: любое пространство классов/имен

Вызывается как: object.Method()

У методов расширения только есть доступ к public членам объекта. Хотя они, кажется, являются членами класса, они не. Это ограничивает то, для чего они полезны. (Методы, требующие доступа к любому членов private или protected, должны содержать не.)

Методы расширения служат, по моему мнению, тремя преимуществами огромными.

  • Предположим, что вы разрабатываете класс A, а класс A содержит около 7 методов. Вы также знаете, что вы хотели бы разработать несколько методов, которые вам не всегда нужны, но это было бы удобно, если вы когда-либо делали. Для этого вы можете использовать методы расширения. Эти методы будут отвлечены в другом классе, который вы можете включить (по классу, благодаря С# 6.0) позже, если вам когда-нибудь понадобится. Необычные методы, которые, как вы знаете, хотите использовать позже, но вы знаете, что вам не всегда нужно.

  • Предположим, вы разрабатываете программу A, и вы используете класс из DLL Something.Other.C, к которому у вас нет источника. Теперь вы хотите добавить метод, который взаимодействует с классом Something.Other.C таким образом, который имел бы смысл с помощью экземпляра или обычного статического метода, но у вас нет источника, поэтому вы не можете! Введите методы расширения, где вы можете определить метод, в котором отображается, как член класса Something.Other.C, но является частью на самом деле вашего кода.

  • Предположим, вы разработали свою собственную библиотеку, которую используете со многими своими приложениями, и вы осознаете в разгар разработки приложения X, что вы действительно можете использовать метод Y для класса A еще раз. Ну, вместо того, чтобы модифицировать определение класса A (потому что это намного больше работает, и вы не используете метод Y где-либо кроме приложения X), вы можете определить метод расширения Y, on class A, который только присутствует в приложении X. Теперь ваши служебные данные метода ограничены строго приложением X. Приложение Z не нуждается в этом методе расширения.

Производительность

Что касается производительности, это будет зависеть от методов, от того, что они делают, и от того, как они это делают. Вы будете подчиняться тем, что public свойства/методы/поля были на объектах, которые вы изменяете, и вам нужно будет оценить производительность. (Если вызов public Value вместо private value приводит к некоторым значительным издержкам проверки, метод экземпляра имеет больше смысла, поскольку он может работать с полями или свойствами private.)

Ответ 3

Короче:

  • Методы экземпляра
    • Свойства экземпляра похожи на существительные. например cat.Color = Color.Blue;
    • Методы экземпляра подобны глаголам. Они должны привести к действию, связанному с типом класса. например cat.Meow();
    • Этот тип метода очень распространен в С#.
  • Статические методы
    • Подумайте об этом как о вспомогательном методе.
    • Статические методы обычно выполняют действие, связанное с типом класса... не конкретный экземпляр.
    • Статические методы должны быть определены при создании класса.
    • Пример: File.Open(String, FileMode) - статический метод, который возвращает FileStream. Здесь нет необходимости иметь экземпляр файла.
  • Методы расширения
    • Подумайте об этом как о вспомогательном методе.
    • Методы расширения определяются после... для существующих сторонних классов, которые вы не можете изменить, но хотели бы, чтобы вы могли.
    • Например: не редко встречаются люди, которые пишут методы расширения для класса DateTime.
    • Нередко возникает дискуссия о том, когда/где должен использоваться метод расширения.

ЛИТЕРАТУРЫ

Ответ 4

Самые большие различия заключаются в следующем:

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

Статические методы и расширения в основном одинаковы: Visual Studio позволяет вам вызывать методы расширения, как если бы они были методом экземпляра, но в конце концов это просто статический метод с переданным им экземпляром.

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

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