重载具有对象和列表的两个函数<;对象>;参数

本文关键字:对象 函数 两个 参数 lt gt 列表 重载 | 更新日期: 2023-09-27 18:28:39

考虑以下代码:

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);
        }
    }  

在第一行中,我用字符串值调用日志,它调用Log(object obj),但在第二行中,用字符串列表new List<string>{"Test","Test2"}调用Log,但编译器调用Log(object obj)而不是Log(List<object> objects)

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

如何使用字符串列表调用第二个日志?

重载具有对象和列表的两个函数<;对象>;参数

List<string>不是List<object>;然而,List<string>一个object-因此选择该过载是完全合理的。尝试:

public static void Log<T>(IList<T> objects)
{
    foreach (var obj in objects)
    {
        Console.WriteLine(obj);
    }
}  

甚至:

public static void Log<T>(IEnumerable<T> objects)
{
    foreach (var obj in objects)
    {
        Console.WriteLine(obj);
    }
}  

你可能还喜欢:

public static void Log(params object[] objects)
{
    foreach (var obj in objects)
    {
        Console.WriteLine(obj);
    }
}

其可用作:

Log("Test","Test2");

Marc Gravell答案的变体:

public static void Log(IReadOnlyList<object> objects)
{
    foreach (var obj in objects)
    {
        Console.WriteLine(obj);
    }
}

这并没有为这个特定的示例添加任何内容,但如果您想像使用List<T>那样使用对集合的索引访问,这可以让您以IEnumerable<object>没有的方式来实现这一点。(是的,有一个LINQ操作符用于枚举的索引访问,但它很笨拙,而且可能非常慢。如果你想明确你的方法需要高效的索引访问的话,IReadOnlyList<object>是很有用的。)

与Marc使用IEnumerable<object>作为参数类型的版本一样,这利用了协方差——与List<T>不同,TIEnumerable<T>IReadOnlyList<T>中是协变的。这意味着,因为stringobject,所以IEnumerable<string>IEnumerable<object>,同样地,IReadOnlyList<string>IReadOnlyList<object>

两个接口的只读特性都很重要。你原来的例子失败的全部原因是List<T>同时支持阅读和写作——如果你递给我一个List<object>,我可以添加任何内容——stringGiraffe或我喜欢的任何内容。这就是为什么List<string>不是List<object>的可接受替代品的原因——我不能在List<string>中添加Giraffe,即使我可以在List<object>中添加一个。但是,由于IEnumerable<T>IReadOnlyList<T>不允许将对象添加到它们所代表的集合中,所以重要的是你可以取出什么,而不是放入什么。从只包含string对象的集合中取出的任何东西都将是object,因为所有东西都是object

是的,我知道你的原始代码没有试图向列表中添加任何内容,但这并不重要——在这种情况下,C#关心的只是函数签名的外观。通过指定IReadOnlyList<object>,您明确表示永远不会尝试修改列表,此时C#知道可以传递List<string>

List<string>不能强制转换为List<Object>。如果您有一个List<Object>,您可以向它添加任何类型的对象。如果您有List<String>,则只能向其添加字符串。因此,不能将List<String>强制转换为List<Object>,因为它不能以相同的方式使用。

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

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

  1. 所有动物都吃
  2. 牛是一种动物
  3. 牛就这样吃

我认为在这里,编译器遵循这个原则,因为:

  1. 可以记录所有对象(public void Log (object obj) {}
  2. List<string>是一个对象
  3. 因此,List<string>可以用作该方法的参数,并被记录