Будут ли полезны «Ограниченные типы» в VB / C #? - PullRequest
8 голосов
/ 16 февраля 2009

Введение

Этот вопрос вызван предложением Марка Гравелла, чтобы я выкладывал на этот сайт новые предложения по языковым функциям, чтобы собрать общее мнение о них.

Идея состоит в том, чтобы собрать, могут ли они быть полезными или, возможно, уже есть другой способ добиться того, чего я добиваюсь.

Предложение (ограниченные типы)

Нормальное объявление переменной в VB.Net записывается так:

Dim SomeVariable as SomeType

Я предлагаю разрешить следующую форму (ы)

Dim SomeVariable1 as {SomeType, ISomeInterface}
Dim SomeVariable2 as {SomeType, ISomeInterface, ISomeOtherInterface}

Этот синтаксис заимствован из стиля ограничения Vb.Net Generics

Зачем это разрешать? ... Для чего это хорошо?

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

Потребитель этих элементов управления через интерфейс потребовал бы, чтобы все созданные элементы управления также реализовывали некоторую серию интерфейсов (только один в моем случае), которые давали всем этим элементам управления дополнительные возможности, которые обычно не обнаруживаются в обычных элементах управления.

Стоит отметить, что следующее в настоящее время не работает.

Public Interface ISpecialControl
End Interface

Public Interface IControlProvider
    Function CreateControl(Of T As {Control, ISpecialControl})() As T
End Interface

Public Class SpecialTextBoxProvider
    Implements IControlProvider
    Public Function CreateControl(Of T As {Control, ISpecialControl})() As T Implements IControlProvider.CreateControl
        Return New SpecialTextBox
    End Function
End Class

Public Class SpecialTextBox
    Inherits TextBox
    Implements ISpecialControl
    Public Sub New()

    End Sub
End Class

Я думаю, что это переводит на C # как:

public interface ISpecialControl
{
}

public interface IControlProvider
{
    T CreateControl<T>()
        where T : Control, ISpecialControl;
}

public class SpecialTextBoxProvider : IControlProvider
{
    public T CreateControl<T>()
        where T : Control, ISpecialControl
    {
        return new SpecialTextBox();
    }
}

public class SpecialTextBox : TextBox, ISpecialControl
{
}

Попытка вернуть "New SpecialTextbox" не удалась из-за невозможности привести SpecialTextbox к T.

"Value of type 'MyApp.SpecialTextBox' cannot be converted to 'T'"

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

Обновление: Идея состоит в том, что эти фабрики могут находиться во внешних (возможно, даже сторонних) сборках и могут зависеть от любых библиотек элементов управления, которые они хотят, создавая и возвращая производные этих элементов управления, которые также реализуют ISpecialControl.

Эти сборки могут быть обнаружены с помощью самоконфигурируемого отражения (Отражение при первом проходе, а затем конфигурация, которая затем используется при дальнейших запусках) и могут использоваться без каких-либо знаний вызывающей сборкой в ​​отношении зависимости этих элементов управления.

Требуется, чтобы эти фабрики были построены, не передавая информацию о контроле, который они должны вызывать, поскольку это победило бы точку.

Так что вы думаете ... Это было бы полезно? ... Есть ли лучший способ достичь этого? Уже есть способ добиться этого?

Ответы [ 7 ]

1 голос
/ 17 февраля 2009

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

  • Duck Typing с помощью динамического набора (входит в VS2010) для значительно большей гибкости, хотя и снижает безопасность типов.
  • Общая композиция

Ваш оригинальный пример не требует ни одного, и его легко заставить работать следующим образом:

public interface IControlProvider<T>
    where T : Control, ISpecialControl
{
    T CreateControl();
}

public class SpecialTextBoxProvider : IControlProvider<SpecialTextBox>
{
    public SpecialTextBox CreateControl()
    {
        return new SpecialTextBox();
    }
}

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

public class ControlProvider : IControlProvider<T>
    where T : Control, ISpecialControl, new()
{
    public T CreateControl()
    {
        return new T();
    }
}

var control = ControlProvider<SpecialTextBox>.CreateControl();

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

0 голосов
/ 14 декабря 2010

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

Однако есть и другой способ достижения базового результата, который вам нужен, хотя и немного неуклюжий. См. Мой вопрос Хранение объекта, который реализует несколько интерфейсов и является производным от определенной базы (.net) , где предлагается лучший подход, который я смог сформулировать.

0 голосов
/ 20 мая 2009

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

Я экспериментировал с классом один раз, чтобы смоделировать это поведение:

public class Choice<T1, T2> 
{
    private readonly T1 value1;
    private readonly T2 value2;

    public Choice(T1 value)
    {
        value1 = value;
    }

    public Choice(T2 value) 
    {
        value2 = value;
    }

    public static implicit operator T1(Choice<T1, T2> choice) 
    {
        return choice.value1;   
    }

    public static implicit operator T2(Choice<T1, T2> choice) 
    {
        return choice.value2;   
    }

    public static implicit operator Choice<T1, T2>(T1 choice) 
    {
        return new Choice<T1, T2>(choice);
    }

    public static implicit operator Choice<T1, T2>(T2 choice) 
    {
        return new Choice<T1, T2>(choice);
    }

    public T1 Value1
    {
        get { return value1; }
    }

    public T2 Value2
    {
        get { return value2; }
    }
}

, который позволяет вам создавать такие конструкции:

[TestMethod]
public void TestChoice() 
{
    TestChoice("Hello");
    TestChoice(new []{"Hello","World"});
}

private static void TestChoice(Choice<string[], string> choice)
{
    string value = choice;
    string[] values = choice;

    if (value != null)
        Console.WriteLine("Single value passed in: " + value);

    if (values != null) {
        Console.WriteLine("Multiple values passed in ");
        foreach (string s in values)
            Console.WriteLine(s + ",");
    }
}

Недостатком неявных операторов является то, что он не выполняет неявное приведение к интерфейсу (он работает приведения из типа интерфейса), поэтому передача IEnumerable<T> вместо string[] не будет работать при попытке привести приведенное значение обратно от Choice<IEnumerable<T>, T> к IEnumerable<T>.

Как правило, я бы предпочел просто использовать перегрузки / интерфейсы BTW, поэтому не поймите меня неправильно:)

Это было бы более полезно в тех случаях, когда вы используете свойства (и не можете использовать перегрузку по этой причине), что-то вроде свойства DataSource, которое допускает только list<string> или list<ListItem>, например.

0 голосов
/ 19 мая 2009

Это существенно отличается от методов расширения ? Почему бы просто не расширить элементы управления?

0 голосов
/ 16 февраля 2009

Я не понимаю, что вы предлагаете, я думаю, что это моя проблема.

Однако для меня это звучит так, как будто вы хотите вернуть элемент управления, который реализует определенный интерфейс.

Почему вы не можете просто сказать, что метод возвращает этот конкретный интерфейс? Мне кажется, что вы говорите: «Я хочу вернуть классы, которые происходят от Control, и которые также реализуют интерфейс ISomeInterface».

Возможно, решение состоит в том, чтобы извлечь необходимые части Control в ваш собственный интерфейс и указать, что вы хотите возвращать объекты, которые реализуют оба?

Как это:

interface IControl { ... }
interface ISomeInterface { ... }

interface IControlInterface : IControl, ISomeInterface { ... }

Этот последний интерфейс будет указывать, что вам нужен объект, который реализует оба базовых интерфейса, что звучит так, как вы хотите, за исключением того, что вы хотите, чтобы одним из требований был базовый класс, а не интерфейс. *

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

0 голосов
/ 16 февраля 2009

Как насчет использования композиции интерфейса?

У вас есть

interface IA;
class B;

если он существует, используйте интерфейс для класса B и составьте

interface IB;
interface IAB : IA, IB;

изменить класс

class C : B, IA

для реализации составного интерфейса (что не должно вызывать проблем)

class C : B, IAB

И вы можете использовать интерфейс IAB в качестве типа ограничения.

0 голосов
/ 16 февраля 2009

Это больше "ограниченные переменные", чем "ограниченные типы" - но, безусловно, интересно. Как и в случае с общими ограничениями, возможно, есть несколько незначительных проблем с разрешением, если есть конфликтующие элементы, но предположительно будут применяться те же обходные пути, что и с общими ограничениями.

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

хм ... интересно.

...