重载具有对象和列表的两个函数<;对象>;参数
本文关键字:对象 函数 两个 参数 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>
不同,T
在IEnumerable<T>
和IReadOnlyList<T>
中是协变的。这意味着,因为string
是object
,所以IEnumerable<string>
是IEnumerable<object>
,同样地,IReadOnlyList<string>
是IReadOnlyList<object>
。
两个接口的只读特性都很重要。你原来的例子失败的全部原因是List<T>
同时支持阅读和写作——如果你递给我一个List<object>
,我可以添加任何内容——string
、Giraffe
或我喜欢的任何内容。这就是为什么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在其简单的解释中声称,如果动物会咬人,那么狗(动物)也应该会咬人。
这就像逻辑中的三段论,它指出:
- 所有动物都吃
- 牛是一种动物
- 牛就这样吃
我认为在这里,编译器遵循这个原则,因为:
- 可以记录所有对象(
public void Log (object obj) {}
) List<string>
是一个对象- 因此,
List<string>
可以用作该方法的参数,并被记录