C# 扩展方法和编译时检查

C# 扩展方法和编译时检查,c#,.net,linq,extension-methods,C#,.net,Linq,Extension Methods,也许有点棘手,但我想知道为什么。在System.Core.dll的System.Linq.Enumerable.cs中,我们有: public static int Count<TSource>(this IEnumerable<TSource> source); 我的邪恶扩展方法正在调用: MyExtension.Count(Enumerable.Range(0,10)) 所以他们是不同的。但我仍然认为这是一种难闻的气味。我们有“真正的”扩展方法吗?哪一个可以阻止这

也许有点棘手,但我想知道为什么。在
System.Core.dll
System.Linq.Enumerable.cs
中,我们有:

public static int Count<TSource>(this IEnumerable<TSource> source);
我的邪恶扩展方法正在调用:

MyExtension.Count(Enumerable.Range(0,10))
所以他们是不同的。但我仍然认为这是一种难闻的气味。我们有“真正的”扩展方法吗?哪一个可以阻止这种邪恶的行为?

看起来C#首先在当前名称空间中查找

在本例中,IL是

.method public hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       27 (0x1b)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  ldc.i4.s   10
  IL_0004:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32,
                                                                                                                                  int32)
  IL_0009:  call       int32 Test.Extensions::Count<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
  IL_000e:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0013:  nop
  IL_0014:  call       int32 [mscorlib]System.Console::Read()
  IL_0019:  pop
  IL_001a:  ret
} // end of method Program::Main
.method public hidebysing static void Main(字符串[]args)cil托管
{
.入口点
//代码大小27(0x1b)
.maxstack 8
IL_0000:没有
IL_0001:ldc.i4.0
IL_0002:ldc.i4.s 10
IL_0004:调用类[mscorlib]System.Collections.Generic.IEnumerable`1[System.Core]System.Linq.Enumerable::Range(int32,
int32)
IL_0009:调用int32 Test.Extensions::Count(类[mscorlib]System.Collections.Generic.IEnumerable`1)
IL_000e:调用void[mscorlib]System.Console::WriteLine(int32)
IL_0013:没有
IL_0014:调用int32[mscorlib]System.Console::Read()
IL_0019:流行音乐
IL_001a:ret
}//方法程序结束::Main
如果我将main方法移动到另一个名称空间(XXX),在这种情况下,编译器会将该方法解析为System.Linq版本

namespace Test
{
    public static class Extensions
    {
        public static int Count<TSource>(this IEnumerable<TSource> source)
        {
            return -1; //evil code
        }
    }

}

namespace XXX{

    public static class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine(Enumerable.Range(0, 10).Count());   // -1, evil code works
            Console.Read();
        }
   }
}


.method public hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       27 (0x1b)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  ldc.i4.s   10
  IL_0004:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32,
                                                                                                                                  int32)
  IL_0009:  call       int32 [System.Core]System.Linq.Enumerable::Count<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
  IL_000e:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0013:  nop
  IL_0014:  call       int32 [mscorlib]System.Console::Read()
  IL_0019:  pop
  IL_001a:  ret
} // end of method Program::Main
名称空间测试
{
公共静态类扩展
{
公共静态整数计数(此IEnumerable源)
{
return-1;//错误代码
}
}
}
名称空间XXX{
公共静态类程序
{
公共静态void Main(字符串[]args)
{
Console.WriteLine(Enumerable.Range(0,10.Count());//-1,邪恶代码可以工作
Console.Read();
}
}
}
.method公共隐藏静态void Main(字符串[]args)cil托管
{
.入口点
//代码大小27(0x1b)
.maxstack 8
IL_0000:没有
IL_0001:ldc.i4.0
IL_0002:ldc.i4.s 10
IL_0004:调用类[mscorlib]System.Collections.Generic.IEnumerable`1[System.Core]System.Linq.Enumerable::Range(int32,
int32)
IL_0009:调用int32[System.Core]System.Linq.Enumerable::Count(类[mscorlib]System.Collections.Generic.IEnumerable`1)
IL_000e:调用void[mscorlib]System.Console::WriteLine(int32)
IL_0013:没有
IL_0014:调用int32[mscorlib]System.Console::Read()
IL_0019:流行音乐
IL_001a:ret
}//方法程序结束::Main
在后一个示例中显式使用您的方法

namespace XXX{
    using Test;
    public static class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine(Enumerable.Range(0, 10).Count());   // -1, evil code works
            Console.Read();
        }

    }
}

.method public hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       27 (0x1b)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  ldc.i4.s   10
  IL_0004:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32,
                                                                                                                                  int32)
  IL_0009:  call       int32 Test.Extensions::Count<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
  IL_000e:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0013:  nop
  IL_0014:  call       int32 [mscorlib]System.Console::Read()
  IL_0019:  pop
  IL_001a:  ret
} // end of method Program::Main
名称空间XXX{
使用测试;
公共静态类程序
{
公共静态void Main(字符串[]args)
{
Console.WriteLine(Enumerable.Range(0,10.Count());//-1,邪恶代码可以工作
Console.Read();
}
}
}
.method公共隐藏静态void Main(字符串[]args)cil托管
{
.入口点
//代码大小27(0x1b)
.maxstack 8
IL_0000:没有
IL_0001:ldc.i4.0
IL_0002:ldc.i4.s 10
IL_0004:调用类[mscorlib]System.Collections.Generic.IEnumerable`1[System.Core]System.Linq.Enumerable::Range(int32,
int32)
IL_0009:调用int32 Test.Extensions::Count(类[mscorlib]System.Collections.Generic.IEnumerable`1)
IL_000e:调用void[mscorlib]System.Console::WriteLine(int32)
IL_0013:没有
IL_0014:调用int32[mscorlib]System.Console::Read()
IL_0019:流行音乐
IL_001a:ret
}//方法程序结束::Main

如果创建一个新类并向两个名称空间中添加using,然后使用在两个名称空间中定义的方法,则错误应该再次出现

扩展方法是通过名称空间来区分的,而不是通过它们在其中声明的静态类来区分的。如果在同一命名空间中定义了两个扩展方法,编译器可以知道采用哪一个方法。

的第7.6.5.2节描述了编译器如何确定哪些扩展方法在范围内,哪些扩展方法优先于其他扩展方法:

搜索C[(候选扩展方法)]的过程如下:

  • 从最近的封闭命名空间声明开始,继续进行每个封闭命名空间声明,并以包含的编译单元结束,连续尝试查找扩展方法的候选集:
    • 如果给定的命名空间或编译单元直接包含具有合格扩展方法Mj的非泛型类型声明Ci,那么这些扩展方法的集合就是候选集合
    • 如果使用给定命名空间或编译单元中的命名空间指令导入的命名空间直接包含具有合格扩展方法Mj的非泛型类型声明Ci,则这些扩展方法的集合就是候选集合

这意味着,如果扩展方法与调用它们的代码位于同一命名空间中,则会选择这些扩展方法。将选择封闭命名空间中的扩展方法,而不是已导入的其他命名空间

我认为,您正在使用相同的重载编写两个方法,这违反了OOPs的原则

如果扩展方法与您的使用方法在相同的命名空间范围内,那么如果两个命名空间中都存在相同的扩展方法,那么它将优先于System.Core.dll(因为它是在使用位置发现的最接近的重载)

为了证明这一点,请将扩展方法名称更改为MyCount1(…)和MyCount2(…),然后它就应该适合您了

namespace Test
{
    public static class Extensions
    {
        public static int Count<TSource>(this IEnumerable<TSource> source)
        {
            return -1; //evil code
        }
    }

}

namespace XXX{

    public static class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine(Enumerable.Range(0, 10).Count());   // -1, evil code works
            Console.Read();
        }
   }
}


.method public hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       27 (0x1b)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  ldc.i4.s   10
  IL_0004:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32,
                                                                                                                                  int32)
  IL_0009:  call       int32 [System.Core]System.Linq.Enumerable::Count<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
  IL_000e:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0013:  nop
  IL_0014:  call       int32 [mscorlib]System.Console::Read()
  IL_0019:  pop
  IL_001a:  ret
} // end of method Program::Main
namespace XXX{
    using Test;
    public static class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine(Enumerable.Range(0, 10).Count());   // -1, evil code works
            Console.Read();
        }

    }
}

.method public hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       27 (0x1b)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  ldc.i4.s   10
  IL_0004:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32,
                                                                                                                                  int32)
  IL_0009:  call       int32 Test.Extensions::Count<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
  IL_000e:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0013:  nop
  IL_0014:  call       int32 [mscorlib]System.Console::Read()
  IL_0019:  pop
  IL_001a:  ret
} // end of method Program::Main