Прежде всего, чего я хочу добиться:
Я хочу расширить тип данных значения, предоставив дополнительные свойства, особенно для проверки диапазонов, предоставленных во время объявления. Я хочу, чтобы новый тип данных также был типом значения.
Сравните с Адой:
subtype Day_Number is Integer range 1 .. 31;
Идеальным, но явно невыполнимым, было бы:
Dim DayNumber As Int64 Range 1 To 31
Тем не менее, я был бы рад:
Dim DayNumber As RangeInt64(1, 31)
Не беда, если инициализация не торопится. После предоставления диапазонов они считаются неизменяемыми. С этого момента тип данных используется только для установки/получения значений, как и с обычными типами значений, только в том случае, если они подлежат проверке на соответствие изначально предоставленному диапазону.
Моя попытка:
Поскольку я не могу наследовать от структур, чтобы расширить их, я попытался включить структуру в структуру в качестве члена.
В модуле у меня есть эта структура:
Friend Structure SRangeValueType(Of T)
Private lMinimum As T
Private lMaximum As T
Friend Property Minimum As T
Get
Return lMinimum
End Get
Set(tValue As T)
lMinimum = tValue
End Set
End Property
Friend Property Maximum As T
Get
Return lMaximum
End Get
Set(tValue As T)
lMaximum = tValue
End Set
End Property
Friend Sub New(Minimum As T, Maximum As T)
lMinimum = Minimum
lMaximum = Maximum
End Sub
End Structure
Я пытаюсь использовать эту общую структуру как член другой структуры (конкретного типа Int64):
Public Structure RangeInt64
Private Range As SRangeValueType(Of Int64)
End Structure
Однако при этом не используется конструктор с двумя аргументами.
Скажем, я хочу инициализировать Range (единственный член структуры RangeInt64) со значениями 100 и 200 для минимума и максимума, соответственно.
Мне не разрешено использовать что-то вроде:
Private Range As SRangeValueType(Of Int64)(100,200)
Каков правильный синтаксис для предоставления моих значений универсальному конструктору?
Если вы хотите вызвать конструктор, вы должны явно использовать ключевое слово New
. Если бы RangeInt64
был классом, вы могли бы использовать Private Range As New SRangeValueType(Of Int64)(100, 200)
, но вы не можете инициализировать поля структуры таким образом, так что вам в основном не повезло. Если вы настаиваете на использовании структуры, вам нужно вызвать метод для инициализации поля. Проще говоря, вам, вероятно, следует использовать классы, а не структуры.
@jmcilhinney Почему вы говорите, что не можете сделать это со структурой? Вы можете создавать конструкторы не по умолчанию для структур. Возьмем, к примеру, Dim x As New Point(1, 2)
...
@StevenDoggart, я ни разу не сказал, что нельзя объявить конструктор для структуры. Я сказал, что вы не можете инициализировать поле структуры, где оно объявлено. Я специально сказал, что вы не можете использовать Private Range As New SRangeValueType(Of Int64)(100, 200)
в RangeInt64
, потому что RangeInt64
— это структура. Если бы RangeInt64
был классом, то эта строка была бы совершенно законной. Я говорю, что RangeInt64
должен быть классом по этой причине, а SRangeValueType
должен быть классом, потому что он изменчив.
@jmcilhinney А. Я пропустил ту часть, где он делал это из другой структуры. К сожалению :)
@jmcilhinney, оказывается, обходных путей и ограничений слишком много, чтобы настаивать на типе значения, поэтому я преобразовал это в класс, точно так же, как вы пытались убедить меня с самого начала.
Обычно, если вы добавляете конструктор в структуру, вы можете вызвать его с помощью ключевого слова New
:
Dim x As SRangeValueType(Of Int64) ' Calls the default, infered, parameter-less constructor
Dim y As New SRangeValueType(Of Int64)(100, 200) ' Calls the explicitly defined constructor
Однако проблема не в этом. Проблема в том, что вы пытаетесь установить значение по умолчанию для неразделяемого поля в структуре. Это то, что никогда не допускается. Все не общие поля в структурах должны по умолчанию иметь значение по умолчанию (т. е. Nothing
). Например:
Public Structure RangeInt64
Private x As Integer = 5 ' Error: Initializers on structure members are valid only for 'Shared' members and constants
Private y As New StringBuilder() ' Error: Non-shared members in a structure cannot be declared 'New'
End Structure
И, как вы, возможно, уже знаете, вы также не можете переопределить предполагаемый конструктор без параметров по умолчанию для структуры:
Public Structure RangeInt64
Public Sub New() ' Error: Structures cannot declare a non-shared 'Sub New' with no parameters
x = 5
y = New StringBuilder()
End Sub
Private x As Integer
Private y As StringBuilder
End Structure
Таким образом, вы застряли. По задумке, когда используется конструктор по умолчанию, все поля в структуре всегда должны по умолчанию иметь значение Nothing
. Однако, если вам действительно нужно, чтобы это была структура, и вы не можете просто преобразовать ее в класс, и вам действительно нужно изменить ее значение по умолчанию, вы теоретически можете подделать ее, чтобы она работала, используя свойство для переноса поле:
Public Structure RangeInt64
Private _Range As SRangeValueType(Of Int64)
Private _RangeInitialized As Boolean
Private Property Range As SRangeValueType(Of Int64)
Get
If Not _RangeInitialized Then
_Range = New SRangeValueType(Of Int64)(100, 200)
_RangeInitialized = True
End If
Return _Range
End Get
Set(value As SRangeValueType(Of Int64))
_Range = value
End Set
End Property
End Structure
Однако само собой разумеется, что это довольно грубо, и его следует избегать, если это возможно.
Теперь, когда вы предоставили более подробную информацию о том, чего вы пытаетесь достичь, я думаю, что у меня может быть лучшее решение для вас. Вы правы в том, что структуры не поддерживают наследование, но поддерживают интерфейсы. Итак, если все, что вам нужно, это набор типов диапазонов, по одному на тип значения, все с одинаковыми минимальными и максимальными свойствами, но все возвращающие разные заранее определенные значения, тогда вы можете сделать что-то вроде этого:
Private Interface IRangeValueType(Of T)
ReadOnly Property Minimum As T
ReadOnly Property Maximum As T
End Interface
Private Structure RangeInt64
Implements IRangeValueType(Of Int64)
Public ReadOnly Property Minimum As Long Implements IRangeValueType(Of Int64).Minimum
Get
Return 100
End Get
End Property
Public ReadOnly Property Maximum As Long Implements IRangeValueType(Of Int64).Maximum
Get
Return 200
End Get
End Property
End Structure
Вы забыли установить _RangeInitialized
в геттере. Кроме того, свойство должно быть Public
.
Упс. Фиксированный. Я не сделал это общедоступным, потому что это не было общедоступным в примере, приведенном в вопросе.
@jmcilhinney, я обновил свой вопрос, указав мотивацию его задать, возможно, есть какой-то другой подход, использующий тип значения для моего намерения. - С другой стороны, почему за этот ответ проголосовали отрицательно? Объяснения и предоставление обходного пути кажутся значимыми.
@Herb Я обновил свой ответ в ответ на обновления вашего вопроса. Что касается того, почему мой ответ был отклонен, я предполагаю, что это просто потому, что кто-то посчитал мой уродливый обходной путь слишком уродливым и что мне не следовало даже упоминать об этом. Способ реализации этого обходного пути, безусловно, является неожиданным поведением, что почти всегда нежелательно, и он немного менее эффективен при каждом чтении, но я не верю, что в этом есть что-то опасное по своей сути, иначе я бы не поделился им.
Эмпирическое правило: никогда не создавайте изменяемые структуры. Однако в случае моего обходного пути, поскольку неинициализированная структура и инициализированная структура будут неразличимы для использования кода, это никогда не приведет к непредвиденным результатам, так что это своего рода исключение из правила. Несмотря на это, я знал, что обходной путь вызовет у людей беспокойство, но я решил, что мой ответ в целом будет вам полезен, поэтому я решил не удалять его.
Добавьте
New
так что,Private Range As New SRangeValueType(Of Int64)(100, 200)
... ИМХО, вам нужно посмотреть на ваши сеттеры, сейчас они не правильно установлены... Например,lMinimum = Value
должно бытьlMinimum = tValue
.