Перегрузка двух функций с помощью параметра object и list <object>

Рассмотрим этот код:

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), но во второй строке я вызываю Log со списком строки new List<string>{"Test","Test2"}, но компилятор вызывает Log(object obj) вместо Log(List<object> objects).

Почему компилятор имеет такое поведение?

Как я могу вызвать второй журнал со списком строк?

Ответ 1

A List<string> не a List<object>; однако List<string> a 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");

Ответ 2

Вариант ответа Марка Гравелла:

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, a IEnumerable<string> является IEnumerable<object>, а также IReadOnlyList<string> является IReadOnlyList<object>.

Важность важности только для чтения обоих интерфейсов. Вся причина, по которой ваш исходный пример терпит неудачу, заключается в том, что List<T> поддерживает чтение и запись - если вы передадите мне List<object>, я могу добавить что-нибудь к нему - a string, a Giraffe или все, что мне нравится. Вот почему List<string> не является приемлемой заменой List<object> - я не могу добавить Giraffe в List<string>, хотя я могу добавить его в List<object>. Но поскольку IEnumerable<T> и IReadOnlyList<T> не позволяют добавлять объекты в коллекции, которые они представляют, все, что имеет значение, - это то, что вы можете вытащить, а не то, что вы можете вставить. Все, что выходит из коллекции, содержащей только string будет object, потому что все a object.

И да, я знаю, что ваш исходный код не пытался добавить что-либо в список, но это не имеет значения - все С# в этом случае заботится о том, как выглядит подпись функции. И, указав IReadOnlyList<object>, вы ясно заявляете, что никогда не будете пытаться изменить список, после чего С# знает, что он ОК, чтобы передать List<string>.

Ответ 3

List<string> нельзя отнести к List<Object>. Если у вас есть List<Object>, вы можете добавлять к нему объекты любого типа. Если у вас есть List<string>, вы можете добавлять к нему только строки. Следовательно, a List<string> нельзя отнести к List<Object>, потому что его нельзя использовать одинаково.

Ответ 4

Я думаю, это хороший пример Liskov Substitution Principal. LSP в своем простом объяснении утверждает, что если животное может укусить, то собака (которая является животным) также должна укусить.

Это похоже на силлогизм в логике, который гласит, что:

  • Все животные едят
  • Корова - животное.
  • Таким образом, корова ест

Я думаю, здесь компилятор следует этому принципу, потому что:

  • Все объекты могут регистрироваться (public void Log (object obj) {})
  • List<string> - это объект
  • Таким образом, List<string> может использоваться как параметр этого метода и записываться в журнал.