Метод расширения и локальная переменная this

Насколько мне известно, this в методе расширения передается как переменная ref. Я могу проверить это, выполнив

public static void Method<T>(this List<T> list)
{
    list.Add(default(T));
}

List<int> ints = new List<int>(new int[] { 1, 2, 3, 4, 5 });
ints.Method();

Моему List<int> ints сейчас 1, 2, 3, 4, 5, 0.

Однако, когда я делаю

public static void Method<T>(this List<T> list, Func<T, bool> predicate)
{
    list = list.Where(predicate).ToList();
}

List<int> ints = new List<int>(new int[] { 1, 2, 3, 4, 5 });
ints.Method(i => i > 2);

Я ожидаю, что мой List<int> ints будет 3, 4, 5, но останется нетронутым. Я упускаю что-то очевидное?


person Arthur Rey    schedule 10.10.2015    source источник
comment
list не передается по ссылке, это ссылка, передаваемая по значению. Передача по ссылке создает псевдоним переменной в вызывающем методе внутри вызываемого объекта, что позволяет вам назначать ее напрямую.   -  person Lee    schedule 10.10.2015
comment
Существует разница между передачей ссылки by и передачей ссылки a. Ссылка на список передается по значению, что означает, что вы можете получить доступ к списку и изменить его, но вы не касаетесь внешней переменной, которая также ссылается на список.   -  person Lasse V. Karlsen    schedule 10.10.2015
comment
(new List<int>()).Method() компилируется => аргумент вообще не должен быть переменной => не так, как ref.   -  person Tom Blodget    schedule 11.10.2015


Ответы (2)


Параметр метода расширения this передается по значению, а не по ссылке. Это означает, что при входе в метод расширения у вас есть две переменные, указывающие на один и тот же адрес памяти: исходный параметр ints и параметр list. Когда вы добавляете элемент в список внутри метода расширения, это отражается в ints, потому что вы изменяете объект, на который ссылаются обе переменные. Когда вы переназначаете list, в управляемой куче создается новый список, и параметр метода расширения указывает на этот список. Переменная ints по-прежнему указывает на старый список.

person Kapol    schedule 10.10.2015

Ну, когда вы пытаетесь изменить свойство некоторого экземпляра класса, вам даже не нужно ref, потому что вы изменяете экземпляр, а не ссылаетесь на него.

В этом примере вам не нужно ключевое слово ref при изменении свойства:

    class MyClass
    {            
        public int MyProperty { get; set; }
    }

    static void Method(MyClass instance)
    {
        instance.MyProperty = 10;                     
    }

    static void Main(string[] args)
    {
        MyClass instance = new MyClass();
        Method(instance);

        Console.WriteLine(instance.MyProperty);
    }

Выход: 10

И здесь вам нужно ключевое слово ref, потому что вы работаете со ссылкой, а не с экземпляром:

    ...

    static void Method(MyClass instance)
    {
        // instance variable holds reference to same object but it is different variable
        instance = new MyClass() { MyProperty = 10 };
    }

    static void Main(string[] args)
    {
        MyClass instance = new MyClass();
        Method(instance);

        Console.WriteLine(instance.MyProperty);
    }

Выход: 0

Это то же самое для вашего сценария, методы расширения такие же, как и обычные статические методы, и если вы создаете новый объект внутри метода, то либо вы используете ключевое слово ref (хотя это невозможно для методов расширения), либо возвращаете этот объект, иначе ссылка на него будет потеряна .

Итак, во втором случае вы должны использовать:

public static List<T> Method<T>(this List<T> list, Func<T, bool> predicate)
{
    return list.Where(predicate).ToList();
}

List<int> ints = new List<int>(new int[] { 1, 2, 3, 4, 5 });
ints = ints.Method(i => i > 2);

foreach(int item in ints) Console.Write(item + " ");

Выход: 3, 4, 5

person Fabjan    schedule 10.10.2015