Обучение
Модуль
Реализация свойств и методов класса - Training
Узнайте, как реализовать свойства класса только для чтения, чтения и записи с помощью модификаторов доступа и доступа, а также как реализовать методы и методы расширения для класса.
Этот браузер больше не поддерживается.
Выполните обновление до Microsoft Edge, чтобы воспользоваться новейшими функциями, обновлениями для системы безопасности и технической поддержкой.
Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Члены расширения позволяют добавлять методы в существующие типы без создания нового производного типа, перекомпиляции или изменения исходного типа.
Начиная с C# 14, существует два синтаксиса, которые используются для определения методов расширения. C# 14 добавляет extension
контейнеры, в которых определяется несколько членов расширения для типа или экземпляра типа. Перед C# 14 добавьте this
модификатор к первому параметру статического метода, чтобы указать, что метод отображается как член экземпляра типа параметра.
Методы расширения являются статическими методами, но они вызываются так же, как если бы они были методами экземпляра в расширенном типе. Для клиентского кода, написанного на C#, F# и Visual Basic, нет видимой разницы между вызовом метода расширения и методами, определенными в типе. Обе формы методов расширения компилируются в один и тот же IL (промежуточный язык). Потребители членов расширения не должны знать, какой синтаксис использовался для определения методов расширения.
Наиболее распространенными элементами расширения являются стандартные операторы запросов LINQ, добавляющие функции запросов к существующим System.Collections.IEnumerable и System.Collections.Generic.IEnumerable<T> типам. Чтобы использовать стандартные операторы запросов, сначала подключите их с помощью директивы using System.Linq
. Затем любой тип, реализующий IEnumerable<T>, по-видимому, имеет такие методы экземпляра, как GroupBy, OrderBy, Average и т. д. Эти дополнительные методы можно увидеть в завершении инструкции IntelliSense, когда вы вводите "точка" после экземпляра типа IEnumerable<T>, например List<T> или Array.
В следующем примере показано, как вызвать стандартный метод оператора OrderBy
запроса в массив целых чисел. Выражение в скобках является лямбда-выражением. Многие стандартные операторы запросов принимают лямбда-выражения в качестве параметров. Дополнительные сведения см. в разделе Лямбда-выражения.
int[] numbers = [10, 45, 15, 39, 21, 26];
IOrderedEnumerable<int> result = numbers.OrderBy(g => g);
foreach (int i in result)
{
Console.Write(i + " ");
}
//Output: 10 15 21 26 39 45
Методы расширения определяются как статические методы, но вызываются с помощью синтаксиса метода экземпляра. Их первый параметр указывает тип, с которым работает метод. Параметр следует этому модификатору. Методы расширения доступны только при явном импорте пространства имен в ваш исходный код с использованием директивы using
.
Начиная с C# 14, можно объявить блоки расширения. Блок расширения — это блок в неложенном, негенерическом, статичном классе, который содержит члены расширения для типа или экземпляра этого типа. В следующем примере кода определяется блок расширения для string
типа. Блок расширения содержит один элемент: метод, который подсчитывает слова в строке:
namespace CustomExtensionMembers;
public static class MyExtensions
{
extension(string str)
{
public int WordCount() =>
str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}
}
Перед C# 14 вы объявляете метод расширения путем добавления this
модификатора в первый параметр:
namespace CustomExtensionMethods;
public static class MyExtensions
{
public static int WordCount(this string str) =>
str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}
Обе формы расширений должны быть определены внутри невложенного негенерированного статического класса.
И его можно вызвать из приложения с помощью синтаксиса для доступа к членам экземпляра:
string s = "Hello Extension Methods";
int i = s.WordCount();
Хотя члены расширения добавляют новые возможности в существующий тип, члены расширения не нарушают принцип инкапсуляции. Объявления доступа для всех членов расширенного типа применяются к членам расширения.
Класс MyExtensions
и метод WordCount
являются static
и к ним можно получить доступ, как и ко всем остальным членам static
. Метод WordCount
можно вызвать так же, как и другие методы static
, следующим образом:
string s = "Hello Extension Methods";
int i = MyExtensions.WordCount(s);
Предыдущий код C# применяется как к блоку расширения, так и к синтаксису this
для членов расширения. Предыдущий код:
string
с именем s
и значением "Hello Extension Methods"
.MyExtensions.WordCount
с заданным аргументом s
.Дополнительные сведения см. в статье о реализации и вызове пользовательского метода расширения.
Как правило, вы вызываете члены расширения функций гораздо чаще, чем реализуете их. Поскольку члены расширения вызываются так, будто они объявлены как члены расширяемого класса, специальных знаний для их использования в клиентском коде не требуется. Чтобы включить члены расширения для определенного типа, достаточно добавить директиву using
для пространства имен, в котором определены методы. Например, чтобы использовать стандартные операторы запросов, добавьте эту using
директиву в код:
using System.Linq;
Члены расширения можно использовать для расширения класса или интерфейса, но не для переопределения поведения, определенного в классе. Член расширения с тем же именем и подписью, что и у членов интерфейса или класса, никогда не вызывается. Во время компиляции члены расширения всегда имеют более низкий приоритет, чем элементы экземпляра (или статические), определенные в самом типе. Другими словами, если тип имеет метод с именем Process(int i)
, и у вас есть метод расширения с той же сигнатурой, компилятор всегда привязывается к методу-члену. Когда компилятор обнаруживает вызов элемента, он сначала ищет совпадение в членах типа. Если совпадение не найдено, он ищет какие-либо члены расширения, определенные для типа. Он привязывается к первому элементу расширения, который он находит. В следующем примере демонстрируются правила, которые компилятор C# использует при определении того, следует ли привязать к члену экземпляра данного типа или к члену расширения. Статический класс Extensions
содержит члены расширения, определенные для любого типа, реализующего IMyInterface
:
public interface IMyInterface
{
void MethodB();
}
// Define extension methods for IMyInterface.
// The following extension methods can be accessed by instances of any
// class that implements IMyInterface.
public static class Extension
{
public static void MethodA(this IMyInterface myInterface, int i) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
public static void MethodA(this IMyInterface myInterface, string s) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
// This method is never called in ExtensionMethodsDemo1, because each
// of the three classes A, B, and C implements a method named MethodB
// that has a matching signature.
public static void MethodB(this IMyInterface myInterface) =>
Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
}
Эквивалентные расширения можно объявить с помощью синтаксиса члена расширения C# 14:
public static class Extension
{
extension(IMyInterface myInterface)
{
public void MethodA(int i) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
public void MethodA(string s) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
// This method is never called in ExtensionMethodsDemo1, because each
// of the three classes A, B, and C implements a method named MethodB
// that has a matching signature.
public void MethodB() =>
Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
}
}
Классы A
и B
C
все реализуют интерфейс:
// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
class A : IMyInterface
{
public void MethodB() { Console.WriteLine("A.MethodB()"); }
}
class B : IMyInterface
{
public void MethodB() { Console.WriteLine("B.MethodB()"); }
public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
}
class C : IMyInterface
{
public void MethodB() { Console.WriteLine("C.MethodB()"); }
public void MethodA(object obj)
{
Console.WriteLine("C.MethodA(object obj)");
}
}
MethodB
Метод расширения никогда не вызывается, так как его имя и подпись точно соответствуют методам, уже реализованным классами. Если компилятор не может найти метод экземпляра с соответствующей сигнатурой, он привязывается к методу соответствующего расширения, если он существует.
// Declare an instance of class A, class B, and class C.
A a = new A();
B b = new B();
C c = new C();
// For a, b, and c, call the following methods:
// -- MethodA with an int argument
// -- MethodA with a string argument
// -- MethodB with no argument.
// A contains no MethodA, so each call to MethodA resolves to
// the extension method that has a matching signature.
a.MethodA(1); // Extension.MethodA(IMyInterface, int)
a.MethodA("hello"); // Extension.MethodA(IMyInterface, string)
// A has a method that matches the signature of the following call
// to MethodB.
a.MethodB(); // A.MethodB()
// B has methods that match the signatures of the following
// method calls.
b.MethodA(1); // B.MethodA(int)
b.MethodB(); // B.MethodB()
// B has no matching method for the following call, but
// class Extension does.
b.MethodA("hello"); // Extension.MethodA(IMyInterface, string)
// C contains an instance method that matches each of the following
// method calls.
c.MethodA(1); // C.MethodA(object)
c.MethodA("hello"); // C.MethodA(object)
c.MethodB(); // C.MethodB()
/* Output:
Extension.MethodA(this IMyInterface myInterface, int i)
Extension.MethodA(this IMyInterface myInterface, string s)
A.MethodB()
B.MethodA(int i)
B.MethodB()
Extension.MethodA(this IMyInterface myInterface, string s)
C.MethodA(object obj)
C.MethodA(object obj)
C.MethodB()
*/
В прошлом было распространено создание классов коллекции, реализующих System.Collections.Generic.IEnumerable<T> интерфейс для заданного типа и содержащих функциональные возможности, которые действовали в коллекциях этого типа. Хотя при создании этого типа объекта коллекции нет ничего плохого, с помощью расширения на объекте System.Collections.Generic.IEnumerable<T>коллекции можно добиться одной и той же функциональности. Расширения имеют преимущество, позволяя вызывать функциональные возможности из любой коллекции, такой как System.Array или System.Collections.Generic.List<T>, которая реализует System.Collections.Generic.IEnumerable<T> для этого типа. Пример использования массива Int32 можно найти ранее в этой статье.
При использовании архитектуры onion или другого многоуровневого приложения обычно используется набор сущностей домена или объектов передачи данных, которые можно использовать для обмена данными между границами приложения. Эти объекты обычно не содержат функциональных возможностей или только минимальных функциональных возможностей, которые применяются ко всем слоям приложения. Методы расширения можно использовать для добавления функциональных возможностей, относящихся к каждому уровню приложений.
public class DomainEntity
{
public int Id { get; set; }
public required string FirstName { get; set; }
public required string LastName { get; set; }
}
static class DomainEntityExtensions
{
static string FullName(this DomainEntity value)
=> $"{value.FirstName} {value.LastName}";
}
Можно объявить эквивалентное FullName
свойство в C# 14 и более поздних версиях с помощью нового синтаксиса блока расширения:
static class DomainEntityExtensions
{
extension(DomainEntity value)
{
string FullName => $"{value.FirstName} {value.LastName}";
}
}
Вместо создания новых объектов при создании повторно используемых функциональных возможностей часто можно расширить существующий тип, например тип .NET или CLR. Например, если вы не используете методы расширения, можно создать Engine
или Query
класс для выполнения запроса на SQL Server, который может вызываться из нескольких мест в нашем коде. Однако вместо этого можно расширить System.Data.SqlClient.SqlConnection класс с помощью методов расширения для выполнения этого запроса в любом месте, где есть подключение к SQL Server. Другие примеры могут быть для добавления общих функций в System.String класс, расширения возможностей System.IO.Stream обработки данных объекта и System.Exception объектов для конкретных функций обработки ошибок. Эти типы вариантов использования ограничены только вашим воображением и хорошим смыслом.
Расширение предопределённых типов может быть затруднительно с типами struct
, так как они передаются по значению в методы. Это означает, что любые изменения структуры вносятся в копию структуры. Эти изменения не отображаются после выхода метода расширения. Вы можете добавить модификатор ref
к первому аргументу, что сделает его методом расширения ref
. Ключевое ref
слово может отображаться до или после ключевого this
слова без каких-либо семантических различий.
ref
Добавление модификатора указывает, что первый аргумент передается по ссылке. Этот метод позволяет создавать методы расширения, изменяющие состояние расширенной структуры (обратите внимание, что частные члены недоступны). Только типы значений или универсальные типы, ограниченные структурой (дополнительные сведения об этих правилах, см struct
. ограничение для получения дополнительных сведений) разрешены в качестве первого параметра ref
метода расширения или в качестве получателя блока расширения. В следующем примере показано, как использовать ref
метод расширения для непосредственного изменения встроенного типа без необходимости переназначить результат или передать его через функцию с ref
ключевым словом:
public static class IntExtensions
{
public static void Increment(this int number)
=> number++;
// Take note of the extra ref keyword here
public static void RefIncrement(this ref int number)
=> number++;
}
Эквивалентные блоки расширения показаны в следующем коде:
public static class IntExtensions
{
extension(int number)
{
public void Increment()
=> number++;
}
// Take note of the extra ref keyword here
extension(ref int number)
{
public void RefIncrement()
=> number++;
}
}
Различные блоки расширений требуются для различения режимов параметров по значению и по ссылке для приемника.
Вы можете увидеть разницу, которую применение ref
к получателю имеет на следующем примере:
int x = 1;
// Takes x by value leading to the extension method
// Increment modifying its own copy, leaving x unchanged
x.Increment();
Console.WriteLine($"x is now {x}"); // x is now 1
// Takes x by reference leading to the extension method
// RefIncrement changing the value of x directly
x.RefIncrement();
Console.WriteLine($"x is now {x}"); // x is now 2
Вы можете применить тот же метод, добавив ref
члены расширения в определяемые пользователем типы структур:
public struct Account
{
public uint id;
public float balance;
private int secret;
}
public static class AccountExtensions
{
// ref keyword can also appear before the this keyword
public static void Deposit(ref this Account account, float amount)
{
account.balance += amount;
// The following line results in an error as an extension
// method is not allowed to access private members
// account.secret = 1; // CS0122
}
}
Предыдущий пример также можно создать с помощью блоков расширений в C# 14:
public static class AccountExtensions
{
extension(ref Account account)
{
// ref keyword can also appear before the this keyword
public void Deposit(float amount)
{
account.balance += amount;
// The following line results in an error as an extension
// method is not allowed to access private members
// account.secret = 1; // CS0122
}
}
}
Эти методы расширения можно получить следующим образом:
Account account = new()
{
id = 1,
balance = 100f
};
Console.WriteLine($"I have ${account.balance}"); // I have $100
account.Deposit(50f);
Console.WriteLine($"I have ${account.balance}"); // I have $150
Рекомендуется добавить функциональные возможности, изменив код объекта или создав новый тип всякий раз, когда это разумно и возможно. Методы расширения являются важным вариантом для создания повторно используемых функций во всей экосистеме .NET. Члены расширения предпочтительнее, если исходный источник не находится под вашим контролем, если производный объект является неуместным или невозможным, или если функциональность имеет ограниченную область.
Дополнительные сведения о производных типах см. в разделе "Наследование".
Если вы реализуете методы расширения для данного типа, помните следующие моменты:
Extensions
все из них попадают в область директивы using Extensions;
.Для реализованной библиотеки классов не следует использовать методы расширения, чтобы избежать увеличения числа версий сборки. Если вы хотите добавить значительные функциональные возможности в библиотеку, для которой принадлежит исходный код, следуйте рекомендациям .NET по управлению версиями сборок. Дополнительные сведения см. в разделе "Управление версиями сборок".
Отзыв о .NET
.NET — это проект с открытым исходным кодом. Выберите ссылку, чтобы оставить отзыв:
Обучение
Модуль
Реализация свойств и методов класса - Training
Узнайте, как реализовать свойства класса только для чтения, чтения и записи с помощью модификаторов доступа и доступа, а также как реализовать методы и методы расширения для класса.