Расширения типов

Расширения типов (также называемые приращениями) — это семейство функций, которые позволяют добавлять новые члены к ранее определенным типам объектов. Доступны следующие три функции.

  • Встроенные расширения типов
  • Необязательные расширения типов
  • Методы расширения

Каждый из них можно использовать в различных сценариях и имеет различные компромиссы.

Синтаксис

// 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

Невозможно получить этот код для работы с дополнительным расширением типа:

  • Как есть, у Sum члена есть другое ограничение 'T ( static member get_Zero и), static member (+) чем определено расширением типа.
  • Изменение расширения типа с тем же ограничением, которое Sum больше не будет соответствовать определенному ограничению в IEnumerable<'T> .
  • Если изменить member this.Sum на, member inline 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 # дает предпочтение методам, не являющимся расширениями, если имеется неоднозначный вызов.

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

См. также