【问题标题】:Why doesn't range indexing work on dynamics为什么范围索引不适用于动态
【发布时间】:2020-05-19 02:09:01
【问题描述】:

我有一些代码,它会抛出。我不明白为什么。

string s="asdf";
Console.WriteLine(s[1..3]);

dynamic d="asdf";
Console.WriteLine(d[1..3]); // throws 
// RuntimeBinderException: The best overloaded method match for 'string.this[int]' has some invalid arguments

有一些静态解析的编译器魔法吗?生成的 IL 表明了这一点。

callvirt    System.String.Substring

有没有办法在动态声明的表达式上使用范围索引?

【问题讨论】:

标签: c# c#-8.0


【解决方案1】:

范围索引是在 C# 8.0 中发布的,它不支持动态,它不能被翻译成另一个代码(在本例中是 text.SubString()),这意味着在运行时无法解析。我在Lambda Tuple C# 8.0 中也遇到了与dynamic 相同的问题。

您可以在代码下方右侧查看how this translation work

public class MyClass {
    public class RangeIndexer
    {
        public string MyString { get; set; }
        public char this[int index] { get => MyString[index]; }
        public string this[Range range] { get => MyString[range]; }
    }

    public void Main() {
        string s = "asdf";
        Console.WriteLine(s[1..3]); // Translate to text.SubString()

        dynamic d = "asdf";
        Console.WriteLine("Index: " + d[1]); // Address to this[int]
        //Console.WriteLine("Range1: " + d[1..3]); // Cannot translate to text.SubString() => Crashed
        Console.WriteLine("Range2: " + d.Substring(1, 2)); // Local method of string
        Console.WriteLine("Range3: " + $"{d}"[1..3]); // Cast as string and translate like Range1

        dynamic rangeIndexer = new RangeIndexer();
        rangeIndexer.MyString = "asdf";
        Console.WriteLine("Range4: " + rangeIndexer[1..3]); // Address to this[range]
    }
}

range indexing 在编译期间由 IDE 转换为 substring(),因此它实际上并未在 string class 中实现,因此解释了为什么只有单个索引 d[1] 有效,因为它已声明。

简而言之,我们有两种选择

选项 1:事实上,dynamic 正在使用反射技术来解析和抓取它是否在变量、方法的范围内……这意味着反射中不会再出现翻译代码。因此,将 dynamic 转换为特定类型将有助于 IDE 基本翻译它们。

选项 2:对象应该像 RangeIndexer 类那样实现,以作为动态类型工作,以确保反射可以抓住它。但几乎经典类型不支持,所以它只适用于您自己的模型。

【讨论】:

    【解决方案2】:

    扩展现有答案

    有一些静态解析的编译器魔法吗?

    是的,有。

    以两种方式支持带范围的索引器(this[System.Range]this[start..end]):显式和隐式。

    明确。如果类型声明索引器this[System.Range],那么只要我们使用符号this[start..end],就会使用这个索引器。编译器将表达式start..end 转换为适当的System.Range 对象并生成调用索引器this[System.Range] 的代码。

    隐式。如果类型不包含索引器this[System.Range],它可以隐式支持范围索引。为了能够隐式支持范围索引,类型必须满足以下条件:

    • 它必须包含LengthCount 属性。
    • 它必须包含带有两个int 参数的Slice(start, end) 方法。类型string 是一个特例;对于string,使用Substring 类型方法代替Slice

    如果类型满足这些条件,那么当我们使用符号this[start..end] 时,编译器会生成调用方法Slice(start, end)(或Substring(start, end) 用于string)的代码。

    更多详情请见C# specification: Ranges


    所以string 类型隐式支持范围索引。

    考虑下一个代码示例:

    dynamic d = "asdf";
    Console.WriteLine(d[1..3]);
    

    在编译过程中无法确定dynamic指的是支持范围索引的对象。因此编译器不会生成调用方法Substring 的代码。相反,它会生成调用索引器this[System.Range] 的代码。 string 类型不包含此类索引器,因此会生成 RuntimeBinderException

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-07-12
    • 2015-11-05
    • 2014-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多