Могу ли я сделать общий необязательный, по умолчанию определенный класс? - программирование
Подтвердить что ты не робот

Могу ли я сделать общий необязательный, по умолчанию определенный класс?

Мой вопрос связан с Есть ли разумный подход к "default" типа в С# Generics?, но с использованием внутреннего универсального класса этот подход не работает.

Данный код выглядит следующим образом:

using System;

public class FooEventArgs<T> : EventArgs
{
    // ... T properties and a constructor
}

public class Foo<T>
{
    public delegate void EventHandler<FooEventArgs>(object sender, FooEventArgs<T> e);
    public event EventHandler<FooEventArgs<T>> Changed
}

И при этом используется следующим образом:

public class User
{
    public Foo<int> foo1;
    public Foo<object> foo2;

    public User()
    {
        foo1 = new Foo<int>();
        foo2 = new Foo<object>();
        foo1.Changed += foo1_Changed;
        foo2.Changed += foo2_Changed;
    }

    protected void foo1_Changed(object sender, FooEventArgs<int> e) { ... }
    protected void foo2_Changed(object sender, FooEventArgs<object> e) { ... }
}

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

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

public class Foo : Foo<object>
{
    public Foo(...) : base(...) { }
}

public class FooEventArgs : FooEventArgs<object>
{
    public Foo(...) : base(...) { }
}

Затем я хочу использовать его следующим образом:

public class User
{
    public Foo foo3;

    public User()
    {
        foo3 = new Foo();
        foo3.Changed += foo3_Changed;
    }

    protected void foo3_Changed(object sender, FooEventArgs e) { ... }
}

Проблема заключается в том, что она, естественно, не будет работать с foo3_Changed принятием FooEventArgs; ему нужно FooEventArgs<object>, так как это событие Foo.Changed получит доступ к нему (поскольку значение будет получено из Foo<object>).

Foo.cs(3,1415926): error CS0123: No overload for 'foo3_Changed' matches delegate 'FooLibrary.Foo<object>.EventHandler<FooLibrary.FooEventArgs<object>>'

Есть ли что-нибудь, что я могу сделать с этим, не дублируя большую часть класса?

Я пробовал еще одну вещь: неявный оператор для преобразования из FooEventArgs<object> в FooEventArgs.

    public static implicit operator FooEventArgs(FooEventArgs<object> e)
    {
        return new FooEventArgs(...);
    }

Это, к сожалению, похоже не работает, хотя я не совсем понимаю, почему:

EditBuffer.cs(13,37): error CS0553: 'FooLibrary.FooEventArgs.implicit operator FooLibrary.FooEventArgs(FooLibrary.FooEventArgs<object>)': user-defined conversions to or from a base class are not allowed

Итак, еще раз, есть ли что-нибудь, что я могу сделать по этому поводу, или я правильно понимаю, что это Tough Luck, и я просто должен быть доволен с помощью FooEventArgs<object> (и тогда, я думаю, я тоже могу просто используйте Foo<object>)?

4b9b3361

Ответ 1

Я не думаю, что вы можете сделать это, если честно. Вы можете сделать Foo вдвойне общим:

public class Foo<TData, TArgs> where TArgs : FooEventArgs<TData>
{
    public delegate void EventHandler<TArgs>(object sender, TArgs e);
    public event EventHandler<TArgs> Changed;
}

Тогда вы могли бы написать:

public class Foo : Foo<object, FooEventArgs>

... но это действительно делает вещи очень сложными для очень мало пользы.

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

Причина, по которой ваше неявное преобразование не работает, не имеет ничего общего с дженериками, кстати, как говорится в сообщении об ошибке, вы не можете объявить преобразование (неявное или явное), которое идет вверх или вниз по иерархии наследования. Из раздела спецификации С# 6.4.1:

С# разрешает объявлять только определенные пользовательские преобразования. В частности, невозможно переопределить уже существующее неявное или явное преобразование.

(Подробнее см. в этом разделе.)


В качестве побочного примечания я нахожу более распространенным использование наследования в обратном порядке для дженериков, как правило, с интерфейсами:

public interface IFoo
{
    // Members which don't depend on the type parameter
}

public interface IFoo<T> : IFoo
{
    // Members which all use T
}

Таким образом, код может получить только IFoo, не беспокоясь о частичных данных, если им не нужно знать T.

К сожалению, это не поможет вам в вашем конкретном случае.

Ответ 2

Еще одна интересная вещь, которую я только что обнаружил, - это то, что вы можете создавать универсальные классы с тем же именем, но разными сигнатурами.

class Foo<T> { 

}

class Foo<T,T> { 

}

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

new Foo<string>();
new Foo<int,double>();
new Foo<string,int>();

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

Полагаю, так работает класс Tuple

public class Tuple<T1, T2, T3... T8> 
{ 
...