Избегание явного универсального типа С#

Скажем, у меня есть общий class с ограничением, при котором T должен реализовывать IWatchable<TKey>, есть ли способ использовать Watcher без явного объявления типа TKey, учитывая, что T все равно будет предоставлять это?

public class Watcher<T, TKey> where T : IWatchable<TKey>
{
}

public interface IWatchable<TKey>
{
    TKey Key { get; }
}

Если я затем хочу использовать Watcher class, я должен объявить TKey вторым типом, несмотря ни на что.

var watcher = new Watcher<BeingWatched, int>();

public class BeingWatched : IWatchable<int> { ... }

Or

var watcher = new Watcher<AlsoBeingWatched<Guid>, Guid>();

public class AlsoBeingWatched<TKey> : IWatchable<TKey> { ... }

person iswinky    schedule 31.08.2016    source источник
comment
Учтите, что система типов позволит типу реализовать общий интерфейс несколько раз (с разными аргументами типа).   -  person Damien_The_Unbeliever    schedule 31.08.2016
comment
@Damien_The_Unbeliever Итак, если BeingWatched реализовал IWatchable<int> и IWatchable<Guid>, то Watcher не знал бы, какую реализацию использовать в BeingWatched? Вот почему он должен быть явным.   -  person iswinky    schedule 31.08.2016
comment
Предложение where используется только для проверки ограничений, а не для разрешения типов... Однако нужно ли вам вообще знать T в Watcher? Может быть достаточно зарегистрировать наблюдателя как public class Watcher<TKey> и работать с IWatchable вместо конкретного типа?   -  person grek40    schedule 31.08.2016


Ответы (1)


Если я правильно понимаю, вы, по сути, хотите, чтобы компилятор выводил один из универсальных типов из другого. Вы можете немного приблизиться к этому, используя метод статического универсального построения, но вам придется пойти на компромисс и заставить Watcher‹T, TKey› реализовать интерфейс только с одним параметром универсального типа. Ниже я попытаюсь проиллюстрировать, а вы сами решите, стоит ли идти на компромисс.

Вот ваш существующий класс Watcher..

public class Watcher<T, TKey> : IWatcher<TKey> where T : IWatchable<TKey>
{
    public Watcher(IWatchable<TKey> target) { }
}

и вот интерфейс, который нужно будет реализовать:

public interface IWatcher<TKey> { }

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

public static class Watcher
{
    public static IWatcher<TKey> For<TKey>(IWatchable<TKey> target)
    {
        return new Watcher<IWatchable<TKey>, TKey>(target);
    }
}

Обратите внимание, что сигнатура типа имеет IWatcher‹TKey› в качестве возвращаемого типа, хотя она создает Watcher‹IWatchable‹TKey›, TKey›. Этот трюк позволяет нам указать только один параметр типа.

Следующий трюк — полагаться на вывод типа C#, чтобы нам не нужно было указывать тип «TKey» при вызове метода «For». Если мы взяли класс:

public class BeingWatched : IWatchable<int>
{
    public BeingWatched(int key)
    {
        Key = key;
    }

    public int Key { get; }
}

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

var watcher = Watcher.For(new BeingWatched(123));

Вывод типа избавляет нас от необходимости явно писать

var watcher = Watcher.For<int>(new BeingWatched(123));

Это работает до тех пор, пока нет двусмысленности. Если у вас есть класс

public class AlsoBeingWatched : IWatchable<int>, IWatchable<Guid>
{
    private readonly int _numberKey;
    private readonly Guid _guidKey;
    public AlsoBeingWatched(int numberKey, Guid guidKey)
    {
        _numberKey = numberKey;
        _guidKey = guidKey;
    }

    int IWatchable<int>.Key { get { return _numberKey; } }

    Guid IWatchable<Guid>.Key { get { return _guidKey; } }
}

тогда

var watcher = Watcher.For(new AlsoBeingWatched(123, Guid.NewGuid()));

не скомпилируется, вы получите ошибку

The type arguments for method 'Watcher.For<TKey>(IWatchable<TKey>)' cannot be inferred from the usage.

Вам нужно будет явно указать либо

var watcher = Watcher.For<int>(new AlsoBeingWatched(123, Guid.NewGuid()));

or

var watcher = Watcher.For<Guid>(new AlsoBeingWatched(123, Guid.NewGuid()));

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

person Dan Roberts    schedule 31.08.2016
comment
Интересный обходной путь, это определенно кажется излишним, но теперь я понимаю, почему тип должен быть явно объявлен. Спасибо хоть! - person iswinky; 02.09.2016