Методы расширения и методы экземпляра против статического класса
Я немного запутался в разных способах использования методов для взаимодействия с объектами в С#, в частности, с основными различиями и последствиями между следующими конструкциями:
- Вызов метода экземпляра
- Использование статического класса в 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()
.