12

Consider this code:

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

In first line i call log with a string value and it invokes Log(object obj) but in the second line i call Log with list of string new List<string>{"Test","Test2"} but compiler invoke Log(object obj) instead of Log(List<object> objects).

Why compiler has this behavior?

How can i call the second log with list of string?


A List<string> is not a List<object>; however, List<string> is an object - so it makes perfect sense to choose that overload. Try instead:

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

or even:

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

You might also like:

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

which can be used as:

Log("Test","Test2");
4

4 回答 4

29

AList<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");
于 2013-08-21T12:18:47.197 回答
7

A variation on Marc Gravell's answer:

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

This doesn't add anything to this particular example, but if you wanted to use indexed access to the collection like you could with a List<T>, this lets you do that in a way that IEnumerable<object> doesn't. (And yes, there's a LINQ operator for indexed access of enumerables, but it's clumsy, and may also be horribly slow. IReadOnlyList<object> is useful if you want to make it clear that your method requires efficient indexed access.)

Like Marc's version that uses IEnumerable<object> as the argument type, this exploits covariance - unlike List<T>, T is covariant in IEnumerable<T> and in IReadOnlyList<T>. That means that because string is an object, an IEnumerable<string> is an IEnumerable<object>, and likewise, IReadOnlyList<string> is an IReadOnlyList<object>.

The read-only nature of both interfaces is important. The whole reason your original example fails is that List<T> supports both reading and writing - if you pass me a List<object> I can add anything to it - a string, a Giraffe or whatever I like. That's why a List<string> is not an acceptable substitute for a List<object> - I can't add a Giraffe to a List<string>, even though I can add one to a List<object>. But because IEnumerable<T> and IReadOnlyList<T> do not allow objects to be added to the collections they represent, all that matter is what you can get out, not what you can put in. Anything that comes out of a collection that contains only string objects will be an object because everything's an object.

And yes, I know your original code didn't attempt to add anything to the list, but that doesn't matter - all C# cares about in this case is how the function signature looks. And by specifying IReadOnlyList<object> you are making clear that you will never attempt to modify the list, at which point C# knows it's OK to pass the List<string>.

于 2013-08-21T13:56:31.290 回答
3

List<string> cannot be cast to List<Object>. If you have a List<Object> you can add objects of any kind to it. If you have a List<String>, you can only add strings to it. Therefore, a List<String> cannot be cast to List<Object>, because it can't be used the same way.

于 2013-08-21T12:22:20.080 回答
1

I guess this is a good example of Liskov Substitution Principal. LSP in its simple explanation claims that if an animal can bite, then a dog (who is an animal) should be able to bite too.

It's like syllogism in logic, which states that:

  1. All animals eat
  2. Cow is an animal
  3. Thus cow eats

I think here, compiler follows this principal, because:

  1. All objects can be logged (public void Log (object obj) {})
  2. List<string> is an object
  3. Thus List<string> can be used as a parameter of that method, and be logged.
于 2013-08-21T12:32:35.297 回答