调用通用容器的索引器时,CS0121模棱两可的方法调用

时间:2019-05-15 19:03:16

标签: c# generics compiler-errors

我正在尝试实现可以​​同时为两种方式建立索引的通用容器:

null

问题是当我尝试为T1和T2使用相同类型的实例时出现编译时错误:

class DoubleIndexer<T1, T2>
{
    public T2 this[T1 key] { get => default; set { } }
    public T1 this[T2 key] { get => default; set { } }
}

当类型T1和T2不同时,没有问题:

var int_int = new DoubleIndexer<int, int>();
int_int[1] = 13; // CS0121: The call is ambiguous between the following
                 // methods or properties: 'DoubleIndexer<T1, T2>.this[T1]'
                 // and 'DoubleIndexer<T1, T2>.this[T2]'

是否有解决此问题的方法,或者我被迫更改了通用类的接口?

2 个答案:

答案 0 :(得分:1)

编码器用于Dictionary ,它在一个方向上工作键>值。鉴于语言的局限性,您应该采用一种更加直观,可预测并适合现有技术的方法。

  1. 仅针对相反情况的子界面

代码:

var lookupByIDReversible = new DoubleIndexer<int, string>();
...
lookupByIDReversible[1] = "hello";
lookupByIDReversible.Reverse["hello"] = 1;

您可能会想到一些更好的语言。

我将让您自己实施。


  1. 以RecordSet为中心,可以使用Nx个索引器

完全另一种方法将利用记录集模式,该模式可以发出保持与集合链接的索引器。该集合是所有索引器的中心。

class Message
{
    public int ID;
    public int ToUserID;
    public int FromUserID
    public string MessageBody;
    public DateTime UpdatedAt;
}

var messages = new RecordSet<Message>();
messages.AddRange(...);
var byToUserID = messages.IndexBy(r => r.ToUserID);
byToUserID[7].FromUserID = 8; //Found member assignment
byToUserID[9] = byToUserID[10]; //Assignment (probably not desirable)
var byMessageBody = messages.IndexBy(r => r.MessageBody);
byMessageBody["hello"].ToUserID = 11;

byToUserID[12] = new Message() { MessageBody = "test1", ... }; //Where ToUserID in the supplied object will be overwritten.
byMessageBody["test1"].ToUserID == 12; //Evaluates to true

这样做的好处是,您将获得一个语义API,该API应该更具可预测性,但是它也可以扩展到两种以上类型。

答案 1 :(得分:-1)

解决方法可以基于explicit interfaces

第二版

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

第一版

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 Indexers in Interfaces

  

仅在避免以下情况时才需要全限定名称:   类使用相同的索引器实现多个接口   签名