Nullable & л; Т > Реализация для VB6 /VBA

Поскольку я был испорчен C # и .NET framework, всякий раз, когда мне приходится работать с VB6, я чувствую, что чего-то не хватает на этом языке. Некоторое время назад я применил List<T> для VB6 ( здесь ), и до этого я реализовал String.Format() и несколько string-helper functions ( здесь ). Не ищите метод StringFormat в спецификациях языка VB6, этот метод является тем, который я написал.

Сегодня мне бы хотелось объявить Nullable<bool> в VB6, поэтому я внедрил класс, который позволил мне сделать это , Я назвал этот класс Nullable и он выглядит примерно так:

Private Type tNullable
    Value As Variant
    IsNull As Boolean
    TItem As String
End Type

Private this As tNullable
Option Explicit

Private Sub Class_Initialize()
    this.IsNull = True
End Sub

Теперь, прежде чем идти дальше, я должен упомянуть, что я использовал «атрибуты процедуры» в свойстве Value, что делает его тип элемент по умолчанию :

Public Property Get Value() As Variant
'default member
    Value = this.Value
End Property

Public Property Let Value(val As Variant) 'damn case-insensitivity...
'default member
    If ValidateItemType(val) Then
        this.Value = val
        this.IsNull = False
    End If
End Property

Public Property Set Value(val As Variant)
'used for assigning Nothing.
'Must be explicitly specified (e.g. Set MyNullable.Value = Nothing; Set MyNullable = Nothing will not call this setter)
    Dim emptyValue As Variant

    If val Is Nothing Then
        this.IsNull = True
        this.Value = emptyValue
    Else
        Err.Raise vbObjectError + 911, "Nullable<T>", "Invalid argument."
    End If

End Property

Закрытый метод ValidateItemType определяет, будет ли тип значения "ok" присвоен как код Value:

Private Function ValidateItemType(val As Variant) As Boolean
    Dim result As Boolean

    If Not IsObject(val) Then
        If this.TItem = vbNullString Then this.TItem = TypeName(val)
        result = IsTypeSafe(val)
        If Not result Then Err.Raise vbObjectError + 911, "Nullable<T>", StringFormat("Type mismatch. Expected '{0}', '{1}' was supplied.", this.TItem, TypeName(val))
    Else
        Err.Raise vbObjectError + 911, "Nullable<T>", "Value type required. T cannot be an object."
        result = False
    End If

    ValidateItemType = result
End Function

Private Function IsTypeSafe(val As Variant) As Boolean
    IsTypeSafe = this.TItem = vbNullString Or this.TItem = TypeName(val)
End Function

Этот механизм заимствован из реализации List<T>, которую я написал ранее, и оказался работоспособным. Вкратце, экземпляр класса Nullable является Nullable<Variant>, пока не будет присвоено значение - если это значение является Integer, тогда экземпляр станет Nullable<Integer> и остается от этого типа - поэтому Value может быть назначен только ---- +: = 17 =: + ----. Механизм можно уточнить, как показано здесь , чтобы быть более гибким (т. е. больше VB-подобного), но на данный момент мне нужно только что-то, что работает.

Остальные члены Integer и HasValue():

ToString()

Использование

Вот несколько тестовых кодов, которые показывают, как можно использовать класс:

Public Property Get HasValue() As Boolean
    HasValue = Not this.IsNull
End Property

Public Function ToString() As String
    ToString = StringFormat("Nullable<{0}>", IIf(this.TItem = vbNullString, "Variant", this.TItem))
End Function

При вызове из немедленной панели этот метод выводит следующее:

Public Sub TestNullable()

    Dim n As New Nullable
    Debug.Print StringFormat("{0} | HasValue: {1} | Value: {2}", n.ToString, n.HasValue, n)

    n = False
    Debug.Print StringFormat("{0} | HasValue: {1} | Value: {2}", n.ToString, n.HasValue, n)

    n = True
    Debug.Print StringFormat("{0} | HasValue: {1} | Value: {2}", n.ToString, n.HasValue, n)

    Set n.Value = Nothing
    Debug.Print StringFormat("{0} | HasValue: {1} | Value: {2}", n.ToString, n.HasValue, n)

    On Error Resume Next
    n = "test" 'expected "Type mismatch. Expected 'T', 'x' was supplied." error
    Debug.Print Err.Description

    n = New List 'expected "Value type required. T cannot be an object." error
    Debug.Print Err.Description

    On Error GoTo 0
End Sub

Я пропустил что-нибудь или это вполне приемлемая реализация?

Меня удивило одна вещь: если я делаю TestNullable Nullable<Variant> | HasValue: False | Value: Nullable<Boolean> | HasValue: True | Value: False Nullable<Boolean> | HasValue: True | Value: True Nullable<Boolean> | HasValue: False | Value: Type mismatch. Expected 'Boolean', 'String' was supplied. Value type required. T cannot be an object. , экземпляр остается Set n.Value = Nothing, как ожидалось. Однако, если я делаю Nullable<Boolean>, не только Set n = Nothing будет печатать Debug.Print n Is Nothing, экземпляр сбрасывается на False и ... setter (Nullable<Variant>) вызывается not - в результате, Интересно, написал ли я класс со встроенной ошибкой, которая делает его un Nothing -able?


Bonus

После дальнейшего тестирования я обнаружил, что это:

Public Property Set Value

Вывод Dim n As New Nullable Set n = Nothing Debug.Print n Is Nothing . Однако это:

False

Вывод Dim n As Nullable Set n = New Nullable Set n = Nothing Debug.Print n Is Nothing (оба фрагмента никогда не попадают в точку останова в True).

Все эти годы я думал, что Set был тем же самым, что и для Dim n As New SomeClass, затем Dim n As SomeClass. Я пропустил меморандум?


UPDATE

Не делайте этого дома.

После тщательного анализа, он появляется Set n = New SomeClass в VB6 абсолютно спорно. Весь класс покупает, является членом Emptyable<T>, который VB6 уже выполняет, с его HasValue.

В принципе, вместо того, чтобы иметь IsEmpty() и делать Nullable<Boolean>, просто объявите MyNullable.HasValue и назначьте его Boolean, и проверьте «пустоту» с помощью Empty.

11 голосов | спросил Mathieu Guindon 14 FebruaryEurope/MoscowbFri, 14 Feb 2014 04:49:00 +0400000000amFri, 14 Feb 2014 04:49:00 +040014 2014, 04:49:00

2 ответа


10

Я думаю, что сам класс может быть неправильно назван, потому что он действительно «пустым» не является Nullable или «Nothing-able».

Вы должны иметь в виду, что Empty, Null и Nothing - это очень разные понятия в VB6. Установка и объект Nothing - это просто синтаксический сахар для освобождения указателя на объект. Это то же самое, что просить ObjPtr () вернуть Null для этого экземпляра (хотя в VB6 нет способа test ), см. Код и пояснения ниже).

Нуль на самом деле лучше концептуализировать в VB6 как тип, а не неинициализированную переменную, как показывает приведенный ниже код:

Dim temp As Variant

'This will return "True"
Debug.Print (temp = Empty)

'This will return "False"
Debug.Print (IsNull(temp))

temp = Null
'This will return "True"
Debug.Print (IsNull(temp))

'This will return "Null"
Debug.Print (TypeName(temp))

Это подводит меня к объяснению, почему ваш класс действительно следует называть «пустым». Вариант лучше всего рассматривать как объект с 2 свойствами - тип и указатель. Если он не инициализирован, он имеет в основном указатель на Nothing и тип Empty. Но это не Null, потому что сам Вариант все еще существует с его «свойствами» по умолчанию.

  

Однако, если я делаю Set n = Nothing, не только Debug.Print n Nothing   будет печатать False, экземпляр сбрасывается на Nullable и   ... setter (значение общего свойства) не называется

Это связано с неприличным поведением VB6 по умолчанию, когда вы используете ссылку на объект, который был установлен в ничто. Он «помогает» создает для вас новый объект, который может быть проверен кодом ниже - перед вторым вызовом ObjPtr (temp) он неявно запускает Set temp = New Test. Вы должны проверить это с помощью Debug.Print в Class_Initialize ().

Private Sub Testing()

    Dim temp As New Test

    Debug.Print (ObjPtr(temp))

    Set temp = Nothing

    'The code below instantiates a new Test object, because it is used after being released.
    Debug.Print (ObjPtr(temp))

End Sub

VB6 рассматривает установку объекта равным Nothing как особый случай, поэтому он никогда не вызывает набор свойств. Что это в основном делает: AddressOf(n) = AddressOf(Nothing).

EDIT: Отличное объяснение того, как варианты работают под капотом здесь .

ответил Comintern 14 FebruaryEurope/MoscowbFri, 14 Feb 2014 06:10:42 +0400000000amFri, 14 Feb 2014 06:10:42 +040014 2014, 06:10:42
7

Добавляя к превосходному ответу @ Comintern, частный тип не нуждается в члене IsNull, поскольку класс принимает только типы значений, правильная семантика для «нулевых» значений - это vbEmpty.

Аксессуар Set, следовательно, не только ошибочен, но и неоднозначен - не только при попытке назначить Nothing, а также потому, что Value является членом по умолчанию, это не сразу видно, что это делает:

Set MyNullable = Nothing

Решение прост: избавиться от аксессуара Set:

Private Type tNullable
    Value As Variant
    TItem As String
End Type

Private this As tNullable
Option Explicit

Public Property Get Value() As Variant
    Value = this.Value
End Property

Public Property Let Value(val As Variant)
    If ValidateItemType(val) Then this.Value = val
End Property    

HasValue можно переписать следующим образом:

Public Property Get HasValue() As Boolean
    HasValue = Not IsEmpty(this.Value)
End Property

И IsTypeSafe должен принять имя типа «Пусто»:

Private Function IsTypeSafe(val As Variant) As Boolean
    IsTypeSafe = this.TItem = vbNullString _
              Or this.TItem = TypeName(val) _
              Or TypeName(val) = "Empty"
End Function

В результате мы можем теперь сделать это:

Dim n As New Nullable
n = False 'n.ToString returns "Nullable<Boolean>"; n.HasValue returns True
n = Empty 'n.ToString returns "Nullable<Boolean>"; n.HasValue returns False
Set n = Nothing 'n.ToString returns "Nullable<Variant>"; n.HasValue returns False

И теперь плохое имя для класса становится больше, чем просто очевидно.

Поэтому метод ToString должен быть изменен, чтобы больше не записывать имя типа:

Public Function ToString() As String
    ToString = StringFormat("{0}<{1}>", TypeName(Me), IIf(this.TItem = vbNullString, "Variant", this.TItem))
End Function

И класс должен быть переименован в Emptyable ... независимо от того, насколько он уродлив: VB6 просто не .NET.

ответил Mathieu Guindon 14 FebruaryEurope/MoscowbFri, 14 Feb 2014 06:58:02 +0400000000amFri, 14 Feb 2014 06:58:02 +040014 2014, 06:58:02

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132