【问题标题】:CS0121 ambiguous method call when calling the indexer of a generic containerCS0121 调用泛型容器的索引器时方法调用不明确
【发布时间】:2019-05-15 19:03:15
【问题描述】:

我正在尝试实现一个可以双向索引的通用容器:

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]'

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

var int_string = new DoubleIndexer<int, string>();
int_string[1] = "Hello"; // OK
int_string["Hello"] = 1; // OK

var int_byte = new DoubleIndexer<int, byte>();
int_byte[1] = 13; // OK
int_byte[(byte)13] = 1; // OK

这个问题有什么解决方法吗,还是我不得不改变我的泛型类的接口?

【问题讨论】:

  • 您自己不觉得这很模棱两可吗?您甚至期望示例中的代码做什么?调用两个索引器?一?随机?
  • @Adrian 他不是在问为什么会这样,而是在问“解决方法”
  • @Adrian 当然是模棱两可的。我正在寻找一种解决歧义的方法,如果可能的话,不要更改我的类的接口。
  • @YuriyFaktorovich 问题是期望的结果是什么。 “解决方法”不足以解释所需的行为。解决方法应该怎么做?
  • @TheodorZoulias 具体类型为intint。它们无法区分。

标签: c# generics compiler-errors


【解决方案1】:

Coders 用于 Dictionary 单向工作 Key > Value。鉴于语言限制,您应该采用更直观、更可预测且符合现有技术的方法。

  1. 子界面只适用于相反的情况

代码:

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

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

我将把实现留给你。


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

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

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,但它也可以扩展到超过 2 种类型。

【讨论】:

  • 感谢托德的回答。这些都是可行的选择!
【解决方案2】:

解决方法可以基于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:

只有在 类正在使用相同的索引器实现多个接口 签名

【讨论】:

  • 也许我应该隐式实现接口,以便在更常见的区分类型的情况下可以省略强制转换。
  • 对,这只是对问题的另一种看法。看起来只使用一个 IInvertedIndex 就足够了。
  • 我无法让它与一个界面一起工作。似乎两者都是必需的(如果我们想保留省略区分类型的强制转换的能力)。
  • 嗯,你能测试一下答案中代码的第一个版本吗(参见最后一个测试 - var int_int = new DoubleIndexer())?它应该可以工作,我检查过。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-18
  • 2011-07-18
  • 1970-01-01
相关资源
最近更新 更多