Конструкция return this

351
09 февраля 2018, 18:06

Всем привет. Возник вопрос, что означает return this;, который обычно встречается в методах? В пользовательском конструкторе this используется когда, поля класса и аргументы конструктора имеют одинаковое имя, как здесь:

public IntValue(int value)
{
   this.value = value;
}

А что означает return this;, не очень понятно? Я думаю эту запись ввели для сокращения кода, и возможно обойтись как-то без return this;, если такое возможно покажите? Если я правильно понял, то код, который я привёл ниже в реализация интерфейса - IEnumerable возвращает экземпляр класса UserCollection приведенный к базовому интерфейсному типу и this в данном случае это и есть ссылка на экземпляр класса UserCollection?

namespace InterIEnumerable
{
    // Класс UserCollection коллекция (набор) объектов класса Element.
    // Для применения foreach, необходимо, чтобы класс реализовывал интерфейс - IEnumerable.
    public class UserCollection : IEnumerable, IEnumerator
    {
        public Element[] elementsArray = null;
        public UserCollection()
        {
            elementsArray = new Element[4];
            elementsArray[0] = new Element("A", 1, 10);
            elementsArray[1] = new Element("B", 2, 20);
            elementsArray[2] = new Element("C", 3, 30);
            elementsArray[3] = new Element("D", 4, 40);
        }
        // Указатель текущей позиции элемента в массиве.
        int position = -1;
        // ------------------------------------------------------------------------------------------------------------------
        // Реализация интерфейса IEnumerator.
        // Передвинуть внутренний указатель (position) на одну позицию.
        public bool MoveNext()
        {
            if (position < elementsArray.Length - 1)
            {
                position++;
                return true;
            }
            else
            {
                return false;
            }
        }
        // Установить указатель (position) перед началом набора.
        public void Reset()
        {
            position = -1;
        }
        // Получить текущий элемент набора. 
        public object Current
        {
            get { return elementsArray[position]; }
        }
        // -----------------------------------------------------------------------------------------------------------------
        // Реализация интерфейса - IEnumerable.
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this as IEnumerator;
        }
    }
}
Answer 1

Ээхм.

this.value — это не магическая конструкция, означающая «не тот value, а другой, который я имею в виду».

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

Внутри нестатических методов объекта this является ссылкой на этот объект. Вне нестатических методов пользоваться this нельзя. Соответственно, если вы возвращаете ссылку на this, вы возвращаете ссылку на объект, в тором выполняется тот самый метод, откуда вы пытаетесь вернуть значение.

Никакой мистики.

Answer 2

Просто чтобы можно было делать не так:

var x = new Smth();
x.DoSmth1();
x.DoSmth2();
x.DoSmth3();
x.DoSmth4();
var res = x.Value;

а вот так (точнее, чтобы сделать эти записи эквивалентными):

var x = new Smth();
var res = x.DoSmth1().DoSmth2().DoSmth3().DoSmth4().Value;

потому что это может быть удобнее и позволять исключить лишнюю переменную.

Это называется чейнингом (chaining) от слова "chain" - "цепь".

кста, а в таких конструкциях есть оверхеад на возвращение обьекта или большее использование стека?

Cтек используется так же, как раньше - ничего не меняется, поскольку вызовы последовательные, а не вложенные. Ну, потенциально может быть разница в 8 байт по сравнению с void, но я почти уверен, что возврат будет идти через регистр, а не через стек; да и 8 байт ни на что не влияют, поскольку они не зависят от числа вызовов.

Сама по себе операция возврата тоже довольно дешёвая, плюс, она может хорошо сочетаться со следующим вызовом в цепочке (mov переместится из кода вызова в код функции) и получится один лишний возврат на всю цепочку.

В общем, я считаю, что никакого оверхэда нет. А если и есть, то ни на что не повлияет - любая операция в коде будет больше, чем этот оверхэд.

https://ideone.com/989mWC - все результаты в пределах погрешности

html, body, #cs, pre { 
  height: 100%; 
  margin: 0; 
} 
 
pre, #cs { 
  display: block; 
  overflow: auto; 
  font-family: monospace; 
  white-space: pre; 
} 
 
pre { 
  width: 14ch; 
  float: left; 
  border-right: 1px solid; 
  margin-right: 8px; 
}
<pre>Успешно	#stdin #stdout 0.03s 15972KB 
360000 3350 
360000 2595 
360000 422 
360000 287 
 
Успешно	#stdin #stdout 0.01s 131648KB 
360000 2241 
360000 2387 
360000 234 
360000 308 
 
Успешно	#stdin #stdout 0.01s 131776KB 
360000 2184 
360000 1382 
360000 181 
360000 238 
 
Успешно	#stdin #stdout 0.01s 131712KB 
360000 1731 
360000 1381 
360000 179 
360000 237</pre> 
 
<script type="text/plain" id="cs">using System; 
using System.Diagnostics; 
  
class Smth 
{ 
  public int Sum; 
  public void Add1(int x) { Sum += x; } 
  public Smth Add2(int x) { Sum += x; return this; } 
} 
  
public class Test 
{ 
  public static void Main() 
  { 
    var x = new Smth(); 
    const int MAXN = 10000; 
  
    Stopwatch sw = new Stopwatch(); 
  
    x.Sum = 0; 
    sw.Start(); 
  
    for (var q=0; q<MAXN; ++q) 
    { 
      x.Add1(1); 
      x.Add1(2); 
      x.Add1(3); 
      x.Add1(4); 
      x.Add1(5); 
      x.Add1(6); 
      x.Add1(7); 
      x.Add1(8); 
    } 
  
    sw.Stop(); 
  
    Console.WriteLine("{0} {1}", x.Sum, sw.ElapsedTicks); 
  
    x.Sum = 0; 
    sw.Restart(); 
  
    for (var q=0; q<MAXN; ++q) 
    { 
      x.Add2(1).Add2(2).Add2(3).Add2(4).Add2(5).Add2(6).Add2(7).Add2(8); 
    } 
  
    sw.Stop(); 
  
    Console.WriteLine("{0} {1}", x.Sum, sw.ElapsedTicks); 
  
    x.Sum = 0; 
    sw.Restart(); 
  
    for (var q=0; q<MAXN; ++q) 
    { 
      x.Add1(36); 
    } 
  
    sw.Stop(); 
  
    Console.WriteLine("{0} {1}", x.Sum, sw.ElapsedTicks); 
  
    x.Sum = 0; 
    sw.Restart(); 
  
    for (var q=0; q<MAXN; ++q) 
    { 
      x.Add2(36); 
    } 
  
    sw.Stop(); 
  
    Console.WriteLine("{0} {1}", x.Sum, sw.ElapsedTicks); 
  } 
}</script>

Answer 3

это ссылка на экхемпляр класса

public Object getThis()
{
   return this;
}
Object me = new Object();
Object  result  = me.getThis() ;теперь result == me
Answer 4

К другим ответам добавлю что

public class MyClass 
{
    int value;
    public SetValue(int value)
    {
        this.value /*(1)*/ = value /*(2)*/;
    }
}

(1) это value самого объекта.
(2) это value из аргумента функции

Это удобно, что бы не плодить новых названий аргументов. И реально было задавать например value не через int newValue, что рождает путаницу а с аргументом с аналогичным именем.

READ ALSO
Не загружается ссылка из сборки на сборку из ресурсов

Не загружается ссылка из сборки на сборку из ресурсов

Извиняюсь за туфтологию, но в общем то проблема примерно так и выглядит

222
C#. WepApi для desktop программы (WPF или WinForms)

C#. WepApi для desktop программы (WPF или WinForms)

Здравствуйте хотел спросить как организовать HTTP WepApi доступ к приложению на WPFНеобходимо реализовать контроль состояния объекта (GET запросы...

228
Счетчик с очередью

Счетчик с очередью

Реализовывал счетчик задач с очередью, но возникает timeout на последней пачке запросов, клиент ожидает и соединение разрываетсяСкорее всего...

243