C# 使用对象和列表重载两个函数<;对象>;参数

C# 使用对象和列表重载两个函数<;对象>;参数,c#,C#,考虑以下代码: static void Main(string[] args) { Log("Test");//Call Log(object obj) Log(new List<string>{"Test","Test2"});;//Also Call Log(object obj) } public static void Log(object obj) { Console.WriteLine(ob

考虑以下代码:

static void Main(string[] args)
    {
        Log("Test");//Call Log(object obj)
        Log(new List<string>{"Test","Test2"});;//Also Call Log(object obj)
    }

    public static void Log(object obj)
    {
        Console.WriteLine(obj);
    }

    public static void Log(List<object> objects)
    {
        foreach (var obj in objects)
        {
            Console.WriteLine(obj);
        }
    }  
static void Main(字符串[]args)
{
日志(“测试”);//调用日志(对象obj)
日志(新列表{“Test”,“Test2”});//也调用日志(objectobj)
}
公共静态无效日志(对象obj)
{
控制台写入线(obj);
}
公共静态无效日志(列出对象)
{
foreach(对象中的var obj)
{
控制台写入线(obj);
}
}  
在第一行中,我用字符串值调用log,它调用
log(objectobj)
,但在第二行中,我用字符串列表
newlist{“Test”,“Test2”}
调用
log(objectobj)
而不是
log(list objects)

为什么编译器有这种行为?

如何使用字符串列表调用第二个日志;然而,
列表
是一个
对象
——因此选择该重载非常有意义。请尝试:

public static void Log<T>(IList<T> objects)
{
    foreach (var obj in objects)
    {
        Console.WriteLine(obj);
    }
}  
它可以用作:

Log("Test","Test2");
列表
不能强制转换为
列表
。 如果您有一个
列表
,您可以向其中添加任何类型的对象。 如果您有一个
列表
,则只能向其中添加字符串。
因此,
列表
不能转换为
列表
,因为它不能以相同的方式使用。

我想这是一个很好的示例。LSP在其简单的解释中声称,如果动物能咬人,那么狗(动物)也应该能咬人

这就像逻辑中的三段论,它指出:

  • 所有动物都吃
  • 牛是一种动物
  • 牛就这样吃
  • 我认为在这里,编译器遵循这个原则,因为:

  • 可以记录所有对象(
    公共作废日志(对象obj){}
  • 列表
    是一个对象
  • 因此,
    List
    可用作该方法的参数,并记录

  • Marc Gravell答案的一个变体:

    public static void Log(IReadOnlyList<object> objects)
    {
        foreach (var obj in objects)
        {
            Console.WriteLine(obj);
        }
    }
    
    publicstaticvoidlog(IReadOnlyList对象)
    {
    foreach(对象中的var obj)
    {
    控制台写入线(obj);
    }
    }
    
    这并没有给这个特定的示例添加任何内容,但是如果您想像使用
    列表那样使用对集合的索引访问,这允许您以
    IEnumerable
    所没有的方式来执行。(是的,有一个LINQ操作符用于对可枚举项进行索引访问,但它很笨拙,而且速度可能非常慢。
    IReadOnlyList
    如果您想明确说明您的方法需要高效的索引访问,那么它很有用。)

    与Marc使用
    IEnumerable作为参数类型的版本一样,它利用了协方差-与
    List
    不同,
    T
    IEnumerable
    IReadOnlyList
    中是协方差的。这意味着因为
    字符串
    是一个
    对象
    ,所以
    IEnumerable
    是一个
    IEnumerable
    ,同样地,
    IReadOnlyList
    也是一个
    IReadOnlyList

    两个接口的只读特性都很重要。您最初的示例失败的全部原因是
    列表
    支持读写-如果您给我一个
    列表
    ,我可以添加任何内容-一个
    字符串
    长颈鹿
    或任何我喜欢的东西。这就是为什么
    列表
    不能替代
    列表
    ——我不能将
    长颈鹿
    添加到
    列表
    ,即使我可以将其添加到
    列表
    。但是,由于
    IEnumerable
    IReadOnlyList
    不允许将对象添加到它们所表示的集合中,所以重要的是您可以从中获取什么,而不是输入什么。任何来自只包含
    字符串
    对象的集合的内容都将成为
    对象
    ,因为所有内容都是
    对象


    是的,我知道您的原始代码没有尝试向列表中添加任何内容,但这并不重要——在本例中,C#关心的是函数签名的外观。通过指定
    IReadOnlyList
    可以清楚地表明您永远不会试图修改列表,此时C#知道可以通过
    list

    传递包含字符串
    Log的对象列表(新列表{“Test”,“Test2”})
    你知道
    列表
    列表
    是不同的类型,对吗?不过,关于差异有一个有趣的旁注:
    字符串[]
    对象[]
    具有相同的特性,但是
    字符串[]
    可以转换为
    对象[]
    。如果通过索引器推入非
    字符串
    值,则会在运行时出错(
    ArrayTypeMismatchException
    )。但它允许您在编译时执行。这解释了为什么
    对象
    重载是有效的,但根据编译器的说法,这并不是为什么它比
    列表
    重载“更好”。而且没有人想解释“为什么”。我说过“更好”吗?不,你没有,这就是问题所在。在这种情况下,
    列表
    重载不是有效的重载。因此,编译器拒绝将其作为选项,并通过重载解析将
    对象
    重载用作“更好”的重载。有几个步骤,包括确定哪些重载是有效的,从中选择“最好的”,然后继续编译该重载。你在第一步的中途停了下来。你的分析是不完整的,即使它不是不正确的。@Saeednamati:+1,:-)但是这个答案指向------------>你知道原因,我也知道原因,
    List
    不是
    List
    ,但我打赌OP不知道为什么,我想有一个链接可以解释为什么(可能是到另一个关于协方差的问题)这会很有帮助。函数没有理由是泛型的——虽然List不能转换为List,但它可以转换为IEnumerable[a]
    Log("Test","Test2");
    
    public static void Log(IReadOnlyList<object> objects)
    {
        foreach (var obj in objects)
        {
            Console.WriteLine(obj);
        }
    }