Разрешение перегрузки работает для обычного метода, но не для конструктора

Моя цель — иметь серию перегрузок, при которых вызывается правильная версия метода в зависимости от типа параметра (известного только во время выполнения). Однако я столкнулся с интересной проблемой в случае, когда метод, который я хочу перегрузить, является конструктором.

Возьмем следующую структуру наследования:

Public MustInherit Class A
    Public Property Common As String
End Class

Public Class X
    Inherits A

    Public Property Unique1 As String
    Public Property Unique2 As String
End Class

Public Class Y
    Inherits A

    Public Property Unique3 As String
    Public Property Unique4 As String
End Class

Базовый класс A наследуется как X, так и Y.

Теперь возьмите этот класс, который я буду использовать, чтобы показать проблему:

Public Class Foo
    Public Sub New(v As X)
        Common = v.Common
        Prop1 = v.Unique1
        Prop2 = v.Unique2
        Prop3 = "Some value"
        Prop3 = String.Empty
    End Sub

    Public Sub New(v As Y)
        Common = v.Common
        Prop1 = "Some value"
        Prop2 = String.Empty
        Prop3 = v.Unique3
        Prop4 = v.Unique4
    End Sub

    Public ReadOnly Property Common As String
    Public ReadOnly Property Prop1 As String
    Public ReadOnly Property Prop2 As String
    Public ReadOnly Property Prop3 As String
    Public ReadOnly Property Prop4 As String

    Public Shared Sub Bar(v As X)
    End Sub

    Public Shared Sub Bar(v As Y)
    End Sub
End Class

Есть обычный метод Bar с перегрузкой, а также конструктор New с перегрузкой. Первый New имеет ту же подпись, что и первый Bar, а второй New имеет ту же подпись, что и второй Bar.

Наконец, возьмите этот тестовый код:

Public Sub Test()
    Dim Param As Object = New X

    'This works fine
    Foo.Bar(Param)

    'This gives a compile error
    Dim Thing As New Foo(Param)
End Sub

Кажется, у компилятора нет проблем с вызовом Bar, но для вызова конструктора я получаю следующую ошибку компиляции:

Разрешение перегрузки не удалось, так как ни один доступный «Новый» не может быть вызван без сужающего преобразования:
«Общедоступная подпрограмма «Новый (v As X)»: Параметр соответствия аргумента «v» сужается от «Объекта» до «X».
'Public Sub New (v As Y)': Параметр сопоставления аргументов 'v' сужается с 'Object' до 'Y'.

Почему вызов конструктора вызывает ошибку, а вызов Bar — нет.

Кроме того, если я изменю объявление Param на Dim Param As A = New X, ни один из них не будет компилироваться.

Я чувствую, что должен понять это, но по какой-то причине я этого не понимаю. Может ли кто-нибудь объяснить мне, почему это не работает, и, возможно, предложить обходной путь?


person Keith Stein    schedule 02.04.2021    source источник
comment
У тебя Option Strict Off, поставь On и будет понятнее. Dim Param As New X() будет работать для обоих.   -  person Jimi    schedule 02.04.2021
comment
@Jimi Dim Param As New X сработает, но в этом нет смысла. Моя цель состоит в том, чтобы иметь серию перегрузок, при которых правильная версия метода вызывается в зависимости от типа параметра (известного только во время выполнения).   -  person Keith Stein    schedule 02.04.2021
comment
Затем реализуйте интерфейс вместо наследования от абстрактного класса. У вас также будет поддержка Generics. Смысл поддержки Option Strict в том, что вы увидите, что обе конструкции терпят неудачу во время компиляции, поэтому вам не нужно спрашивать себя, почему одна из них терпит неудачу?, потому что на самом деле обе конструкции терпят неудачу, это просто временно скрыто от вас.   -  person Jimi    schedule 02.04.2021
comment
@Jimi Обе пары не терпят неудачу. Перегрузки Bar работают как положено. Dim Param As Object = New X приводит к вызову Bar(v As X), а Dim Param As Object = New Y приводит к вызову Bar(v As Y). Только перегрузки конструктора вызывают какую-либо ошибку.   -  person Keith Stein    schedule 02.04.2021
comment
@Jimi Реализация интерфейса не работает для меня, потому что X и Y (в реальном коде) имеют разные члены, которые уникальны, и разные перегрузки методов должны обрабатывать два типа по-разному.   -  person Keith Stein    schedule 02.04.2021
comment
Вы все еще имеете в виду код, протестированный с помощью Option Strict Off. Установив его On, как построение Foo, так и вызов Bar() не разрешены, потому что не разрешено неявное преобразование (как и должно быть). Это похоже на разрешение неявного преобразования между типами, скрытие сужающих или расширяющих преобразований. Таким образом, вы суммируете целые числа со строками с автоматическим обнадеживающим преобразованием между тем или иным типом. Удачи. -- Реализация интерфейса не имеет ничего общего с конкретными реализациями или наличием специализированных методов/свойств в классах, реализующих интерфейс.   -  person Jimi    schedule 02.04.2021
comment
Вы даже можете использовать интерфейс, который не определяет какой-либо метод или свойство, и в любом случае у вас есть поддержка Generics.   -  person Jimi    schedule 02.04.2021
comment
Неявное преобразование @Jimi не разрешено (как и должно быть). Как должно быть, это мнение. Option Strict вариант. Я понимаю, что есть преимущества, но я хотел бы избежать обсуждения плюсов и минусов в этих комментариях. В приведенном выше коде Option Strict равно Off; разрешено неявное преобразование. Тем не менее, кажется, я не понимаю, как вы хотите, чтобы я реализовал и использовал интерфейс. Если вы хотите опубликовать ответ с некоторым кодом, показывающим мне пример этого, я был бы рад его увидеть.   -  person Keith Stein    schedule 02.04.2021
comment
Извините, но я согласен с @Jimi здесь. Вы будете преследовать множество потенциальных проблем с вашим кодом, если будете полагаться на среду выполнения, чтобы разобраться с этим. (Что вы будете делать, если кто-то передаст экземпляр класса Z, который наследует A, а соответствующего конструктора или общего метода нет?) Я искал и не смог найти простого ответа на вопрос, почему Option Strict Off кажется игнорируемым для конструкторов.   -  person Sean Skelly    schedule 03.04.2021
comment
Если вы хотите, чтобы наша помощь помогла решить вашу более важную задачу (взять объект неизвестного типа, производный от A, во время выполнения и убедиться, что над ним запускается правильная функция), вам необходимо предоставить нам дополнительную информацию о том, как может выглядеть эта функция. как. Добавьте больше мяса к костям вашего примера кода, и тогда мы, вероятно, сможем показать вам, как выглядит интерфейсное решение.   -  person Sean Skelly    schedule 03.04.2021
comment
@SeanSkelly В случае такого класса Z я бы ожидал исключения. Что касается вашего запроса на дополнительную информацию, я добавил в свой пример кода. Дайте мне знать, если вам нужна дополнительная информация, и я постараюсь соответствующим образом обновить свой вопрос.   -  person Keith Stein    schedule 03.04.2021
comment
Извините, проблема с Option Strict Off заключается не только в том, что это Off (со всеми вытекающими отсюда последствиями), но и в том, что вы затем структурируете свое приложение, имея в виду своего рода слабо типизированную модель, поэтому слишком много частей кода полагаются на эту не- функция (сделанная для облегчения миграции VB6), пытаясь игнорировать строго типизированную платформу, с которой вы работаете. Это генерирует код, который просто скрывает проблемы. -- Вы пытаетесь построить общую структуру, но не хотите говорить об интерфейсах. .Net использует для этого интерфейсы, а не тип Object, и наследование ограничено одним классом.   -  person Jimi    schedule 03.04.2021
comment
Все, что вам уже сказали, более чем справедливо. Но другой вариант, с которым вы можете пойти, - это иметь конструктор, который фактически принимает параметр объекта, а затем внутри него проверяет тип и вызывает соответствующий конструктор класса.   -  person Hursey    schedule 03.04.2021
comment
Короче говоря, то, с чем вы сталкиваетесь, на 100% правильно и достоверно. Вы вызываете конструктор с параметром объекта, а не с классами, которые вы определили   -  person Hursey    schedule 03.04.2021
comment
@Hursey ... проверьте тип и вызовите соответствующий конструктор класса. Разве вы не описываете здесь подход с использованием фабричного шаблона? Звучит правильно, хотя я думаю, что интерфейс был бы более простым подходом.   -  person SteveCinq    schedule 05.04.2021


Ответы (2)


Хотя до сих пор неясно, чего именно вы пытаетесь достичь, ответ — единственное разумное место для обмена кодом. Вот попытка решить вашу проблему с Option Strict On, используя интерфейс для определения свойств, которые должен иметь класс, чтобы быть переданным Foo для его построения.

Обратите внимание на комментарии в коде, которые также помогают объяснить ситуацию.

Это абстрагирует вещи, так что Foo не нужно знать обо всех типах, производных от A — он знает только об интерфейсе. Фактически, он «инвертирует» отношение, чтобы A и его производные типы знали, что необходимо для Foo (для каждого интерфейса). Остальное — это реализация X и Y, где теперь живут определения предложений с 1 по 4 (вместо различных перегруженных конструкторов Foo). Это сокращает количество конструкторов для Foo до одного.

Логика преобразования свойств класса, производного от A, в свойства Foo должна где-то жить. Выталкивая эту логику из Foo в производные классы, вы можете избежать проблем сужения, которые Option Strict Off откладывает до времени выполнения. Кроме того, добавить новый класс Z, производный от A, очень просто, и вам не нужно изменять Foo, чтобы сразу же использовать его.

Но опять же, поскольку не совсем ясно, что вы собираетесь делать с этим примером кода, трудно понять, «работает» ли этот подход для того, о чем вы думаете, или нет.

Option Strict On

Module Module1
    Sub Main()
        Dim Param As A = New X
        Dim Thing As New Foo(Param)

        Param = New Y
        Thing = New Foo(Param)

        'if you make a new class Z which Inherits A, it will immediately be translatable to Foo
        'albeit with all String.Empty properties unless you override the properties from A
    End Sub

End Module

'Defines what a Foo wants, what a Foo needs
Public Interface IPropertiesForFoo
    ReadOnly Property Common As String
    ReadOnly Property Prop1 As String
    ReadOnly Property Prop2 As String
    ReadOnly Property Prop3 As String
    ReadOnly Property Prop4 As String
End Interface

Public MustInherit Class A
    Implements IPropertiesForFoo

    Public Property Common As String


#Region "IPropertiesForFoo implementation"
    'these are Overridable, so derived classes can choose what to change and what not to
    'note these are all Protected, so only derived classes know about them.  Users of A may not care.

    'This is just one choice;
    ' you could also use Throw New NotImplementedException (instead of Return String.Empty)
    ' and force derived classes to handle every property

    Protected Overridable ReadOnly Property IPropertiesForFoo_Prop1 As String Implements IPropertiesForFoo.Prop1
        Get
            Return String.Empty
        End Get
    End Property

    Protected Overridable ReadOnly Property IPropertiesForFoo_Prop2 As String Implements IPropertiesForFoo.Prop2
        Get
            Return String.Empty
        End Get
    End Property

    Protected Overridable ReadOnly Property IPropertiesForFoo_Prop3 As String Implements IPropertiesForFoo.Prop3
        Get
            Return String.Empty
        End Get
    End Property

    Protected Overridable ReadOnly Property IPropertiesForFoo_Prop4 As String Implements IPropertiesForFoo.Prop4
        Get
            Return String.Empty
        End Get
    End Property

    'private, and doesn't need to be Overridable, as Common can map directly
    Private ReadOnly Property IPropertiesForFoo_Common As String Implements IPropertiesForFoo.Common
        Get
            Return Common
        End Get
    End Property
#End Region
End Class

Public Class X
    Inherits A

    Public Property Unique1 As String
    Public Property Unique2 As String

    Protected Overrides ReadOnly Property IPropertiesForFoo_Prop1 As String
        Get
            Return Unique1
        End Get
    End Property

    Protected Overrides ReadOnly Property IPropertiesForFoo_Prop2 As String
        Get
            Return Unique2
        End Get
    End Property

    Protected Overrides ReadOnly Property IPropertiesForFoo_Prop3 As String
        Get
            Return "Some value"
        End Get
    End Property

    'doesn't need to override Prop4; leave it as String.Empty
End Class

Public Class Y
    Inherits A

    Public Property Unique3 As String
    Public Property Unique4 As String

    Protected Overrides ReadOnly Property IPropertiesForFoo_Prop1 As String
        Get
            Return "Some value"
        End Get
    End Property

    'doesn't need to override Prop2; leave it as String.Empty

    Protected Overrides ReadOnly Property IPropertiesForFoo_Prop3 As String
        Get
            Return Unique3
        End Get
    End Property

    Protected Overrides ReadOnly Property IPropertiesForFoo_Prop4 As String
        Get
            Return Unique4
        End Get
    End Property
End Class


Public Class Foo

    Public Sub New(v As IPropertiesForFoo)
        Common = v.Common
        Prop1 = v.Prop1
        Prop2 = v.Prop2
        Prop3 = v.Prop3
        Prop4 = v.Prop4
    End Sub

    Public ReadOnly Property Common As String
    Public ReadOnly Property Prop1 As String
    Public ReadOnly Property Prop2 As String
    Public ReadOnly Property Prop3 As String
    Public ReadOnly Property Prop4 As String


End Class

Если на то пошло, в зависимости от того, что на самом деле делает остальная часть Foo, вам может даже не понадобиться Foo - просто пропустите свои экземпляры A, поскольку они также IPropertiesForFoo. Затем извлеките их свойства, помеченные как Prop1, Prop2, по мере необходимости. (Опять же, ваш упрощенный пример исходного кода недостаточно намекает на более широкий контекст, чтобы понять, подходит ли этот подход или нет.)

person Sean Skelly    schedule 02.04.2021

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

Public Class Foo
    Public Sub New(v As X)
    End Sub

    Public Sub New(v As Y)
    End Sub

    Public Shared Function Create(v As X) As Foo
        Return New Foo(v)
    End Function

    Public Shared Function Bar(v As Y) As Foo
        Return New Foo(v)
    End Function
End Class

Что позволяет мне использовать Foo вот так:

Dim Param As Object = New Y
Foo.Create(Param)

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

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

person Keith Stein    schedule 05.04.2021