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

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

  • Расширения встроенных типов
  • Дополнительные расширения типов
  • Методы расширения

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

Синтаксис

// 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# предпочтительнее использовать методы, не являющиеся расширениями, если есть неоднозначный вызов.

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

См. также