Расширения типов
Расширения типов (также называемые расширениями) — это семейство функций, которые позволяют добавлять новые элементы в ранее определенный тип объекта. Ниже приведены три функции.
- Расширения встроенных типов
- Дополнительные расширения типов
- Методы расширения
Каждый из них может использоваться в разных сценариях и имеет разные компромиссы.
Синтаксис
// Intrinsic and optional extensions
type typename with
member self-identifier.member-name =
body
...
// Extension methods
open System.Runtime.CompilerServices
[<Extension>]
type Extensions() =
[<Extension>]
static member extension-name (ty: typename, [args]) =
body
...
Расширения встроенных типов
Расширение встроенного типа — это расширение типа, которое расширяет определяемый пользователем тип.
Расширения встроенных типов должны быть определены в том же файле и в том же пространстве имен или модуле, что и расширение типа. Любое другое определение приведет к тому, что они будут необязательными расширениями типов.
Расширения встроенных типов иногда являются более понятным способом разделения функциональных возможностей из объявления типа. В следующем примере показано, как определить расширение встроенного типа:
namespace Example
type Variant =
| Num of int
| Str of string
module Variant =
let print v =
match v with
| Num n -> printf "Num %d" n
| Str s -> printf "Str %s" s
// Add a member to Variant as an extension
type Variant with
member x.Print() = Variant.print x
Использование расширения типа позволяет разделить каждое из следующих элементов:
- Объявление
Variant
типа - Функции печати
Variant
класса в зависимости от его "фигуры" - Способ доступа к функциям печати с помощью нотации в стиле
.
объектов
Это альтернатива определению всего как члена Variant
. Хотя это не по сути лучший подход, это может быть более чистое представление функциональных возможностей в некоторых ситуациях.
Встроенные расширения типов компилируются как члены типа, который они расширяют, и отображаются на типе, когда тип проверяется отражением.
Дополнительные расширения типов
Дополнительное расширение типа — это расширение, которое отображается вне исходного модуля, пространства имен или сборки расширенного типа.
Дополнительные расширения типов полезны для расширения типа, который вы не определили самостоятельно. Например:
module Extensions
type IEnumerable<'T> with
/// Repeat each element of the sequence n times
member xs.RepeatElements(n: int) =
seq {
for x in xs do
for _ in 1 .. n -> x
}
Теперь вы можете получить доступRepeatElements
, как будто он является членомIEnumerable<T>, если Extensions
модуль открыт в область, в которых вы работаете.
Необязательные расширения не отображаются в расширенном типе при проверке отражением. Необязательные расширения должны находиться в модулях, и они доступны только в область, если модуль, содержащий расширение, открыт или находится в область.
Необязательные члены расширения компилируются в статические элементы, для которых экземпляр объекта передается неявно в качестве первого параметра. Однако они действуют так, как будто они являются членами экземпляра или статическими элементами в соответствии с тем, как они объявлены.
Необязательные члены расширения также не видны потребителям C# или Visual Basic. Их можно использовать только в другом коде F#.
Универсальное ограничение встроенных и необязательных расширений типов
Можно объявить расширение типа в универсальном типе, где переменная типа ограничена. Требование заключается в том, что ограничение объявления расширения соответствует ограничению объявленного типа.
Однако даже если ограничения совпадают между объявленным типом и расширением типа, можно определить ограничение текстом расширенного элемента, которое накладывает другое требование к параметру типа, чем объявленный тип. Например:
open System.Collections.Generic
// NOT POSSIBLE AND FAILS TO COMPILE!
//
// The member 'Sum' has a different requirement on 'T than the type IEnumerable<'T>
type IEnumerable<'T> with
member this.Sum() = Seq.sum this
Невозможно получить этот код для работы с дополнительным расширением типа:
- Как и в случае, член имеет другое ограничение (
static member get_Zero
'T
иstatic member (+)
) по сравнению с тем,Sum
что определяет расширение типа. - Изменение расширения типа таким же ограничением, что
Sum
и определенное ограничениеIEnumerable<'T>
. member inline this.Sum
Изменениеmember this.Sum
присвоит ошибке несоответствие ограничений типов.
То, что нужно, являются статическими методами, которые "плавают в пространстве" и могут быть представлены так, как если бы они расширяли тип. Здесь необходимы методы расширения.
Методы расширения
Наконец, методы расширения (иногда называемые элементами расширения стиля C#) можно объявить в F# как статический метод-член класса.
Методы расширения полезны, если вы хотите определить расширения в универсальном типе, который будет ограничивать переменную типа. Например:
namespace Extensions
open System.Collections.Generic
open System.Runtime.CompilerServices
[<Extension>]
type IEnumerableExtensions =
[<Extension>]
static member inline Sum(xs: IEnumerable<'T>) = Seq.sum xs
При использовании этот код будет отображаться так, как если Sum
бы он был определенIEnumerable<T>, если Extensions
он открыт или находится в область.
Чтобы расширение было доступно для VB.NET кода, необходимо дополнительное ExtensionAttribute
значение на уровне сборки:
module AssemblyInfo
open System.Runtime.CompilerServices
[<assembly:Extension>]
do ()
Другие замечания
Расширения типов также имеют следующие атрибуты:
- Любой тип, к которому можно получить доступ, может быть расширен.
- Встроенные и необязательные расширения типов могут определять любой тип элемента, а не только методы. Поэтому свойства расширения также возможны, например.
- Маркер
self-identifier
в синтаксисе представляет экземпляр вызываемого типа, как и обычные члены. - Расширенные члены могут быть статическими или экземплярами.
- Переменные типа в расширении типа должны соответствовать ограничениям объявленного типа.
Для расширений типов также существуют следующие ограничения:
- Расширения типов не поддерживают виртуальные или абстрактные методы.
- Расширения типов не поддерживают переопределение методов в качестве расширений.
- Расширения типов не поддерживают статически разрешенные параметры типа.
- Необязательные расширения типов не поддерживают конструкторы в качестве расширений.
- Расширения типов нельзя определить для аббревиаций типа.
- Расширения типов недопустимы
byref<'T>
(хотя их можно объявить). - Расширения типов недопустимы для атрибутов (хотя их можно объявить).
- Вы можете определить расширения, которые перегружают другие методы того же имени, но компилятор F# предпочтительнее использовать методы, не являющиеся расширениями, если есть неоднозначный вызов.
Наконец, если для одного типа существуют несколько расширений встроенных типов, все члены должны быть уникальными. Для расширений необязательных типов члены в разных расширениях типов для одного типа могут иметь одинаковые имена. Ошибки неоднозначности возникают только в том случае, если клиентский код открывает два разных область, определяющих одинаковые имена элементов.