Обходной путь может быть основан на явных интерфейсах :
2-я версия
public interface IDirectIndex<T1, T2>
{
T2 this[T1 key] { get; set; }
}
public interface IInvertedIndex<T1, T2>
{
T1 this[T2 key] { get; set; }
}
internal class DoubleIndexer<T1, T2> : IDirectIndex<T1, T2>, IInvertedIndex<T1, T2>
{
T2 IDirectIndex<T1, T2>.this[T1 key]
{
get => default;
set { Console.WriteLine($"T2 IDirectIndex<T1, T2>.this[T1 key]: {value}"); }
}
T1 IInvertedIndex<T1, T2>.this[T2 key]
{
get => default;
set { Console.WriteLine($"T1 IInvertedIndex<T1, T2>.this[T2 key]: {value}"); }
}
}
Примеры испытаний:
var int_string = new DoubleIndexer<int, string>();
((IDirectIndex<int, string>)int_string)[1] = "Hello"; // OK
((IInvertedIndex<int, string>)int_string)["Hello"] = 1; // OK
var int_byte = new DoubleIndexer<int, byte>();
((IInvertedIndex<int, byte>)int_byte)[1] = 134567; // OK
((IDirectIndex<int, byte>)int_byte)[134567] = 41; // OK
var int_int = new DoubleIndexer<int, int>();
((IInvertedIndex<int, int>)int_int)[1] = 1345; // OK
((IDirectIndex<int, int>)int_int)[13] = 5431; // OK
1-я версия
public interface IIndex<T1, T2>
{
T2 this[T1 key] { get; set; }
}
internal class DoubleIndexer<T1, T2> : IIndex<T1, T2>
{
public T1 this[T2 key]
{
get => default;
set { Console.WriteLine($"T1 this[T2 key]: {value}"); }
}
T2 IIndex<T1, T2>.this[T1 key]
{
get => default;
set { Console.WriteLine($"T2 IIndex<T1, T2>.this[T1 key]: {value}"); }
}
}
Примеры испытаний:
var int_string = new DoubleIndexer<int, string>();
((IIndex<int, string>)int_string)[1] = "Hello"; // OK
int_string["Hello"] = 1; // OK
var int_byte = new DoubleIndexer<int, byte>();
int_byte[1] = 134567; // OK
((IIndex<int, byte>)int_byte)[134567] = 41; // OK
var int_int = new DoubleIndexer<int, int>();
int_int[1] = 1345; // OK
((IIndex<int, int>)int_int)[13] = 5431; // OK
Индексаторы MSDN в интерфейсах :
полное имя необходимо только для избежания двусмысленности, когда класс реализует более одного интерфейса с одной и той же сигнатурой индексатора