C# Language
Тип значения vs Тип ссылки
Поиск…
Синтаксис
- Передача по ссылке: public void Double (ref int numberToDouble) {}
замечания
Вступление
Типы значений
Типы значений являются более простыми из двух. Типы значений часто используются для представления самих данных. Целое число, булево или точка в трехмерном пространстве - все примеры хороших типов значений.
Типы значений (structs) объявляются с помощью ключевого слова struct. См. Раздел синтаксиса для примера того, как объявить новую структуру.
Вообще говоря, у нас есть 2 ключевых слова, которые используются для объявления типов значений:
- Структуры
- Перечисления
Типы ссылок
Ссылочные типы немного сложнее. Ссылочные типы являются традиционными объектами в смысле объектно-ориентированного программирования. Таким образом, они поддерживают наследование (и преимущества там), а также поддерживают финализаторы.
В C #, как правило, мы имеем такие ссылочные типы:
- Классы
- Делегаты
- Интерфейсы
Новые ссылочные типы (классы) объявляются с использованием ключевого слова class. Например, см. Раздел синтаксиса о том, как объявить новый тип ссылки.
Основные отличия
Основные различия между ссылочными типами и типами значений приведены ниже.
Типы значений существуют в стеке, ссылочные типы существуют в куче
Это часто упоминается различие между ними, но на самом деле то, что сводится к тому, что, когда вы используете тип значения в C #, например int, программа будет использовать эту переменную для прямого обращения к этому значению. Если вы скажете int mine = 0, то переменная шахта относится непосредственно к 0, что является эффективным. Однако ссылочные типы фактически удерживают (как следует из названия) ссылку на базовый объект, это похоже на указатели на других языках, таких как C ++.
Вы можете не заметить эффекты этого немедленно, но эффекты там, мощные и тонкие. См. Пример изменения типов ссылок в другом месте для примера.
Это различие является основной причиной следующих различий и заслуживает внимания.
Типы значений не изменяются при изменении их в методе, типы ссылок делают
Когда тип значения передается в метод в качестве параметра, если метод каким-либо образом изменяет значение, значение не изменяется. Напротив, передача ссылочного типа в тот же самый метод и его изменение изменят базовый объект, так что другие вещи, которые используют тот же самый объект, будут иметь вновь измененный объект, а не его исходное значение.
См. Пример типов значений по сравнению с типами ссылок в методах для получения дополнительной информации.
Что делать, если я хочу их изменить?Просто передайте их в свой метод, используя ключевое слово «ref», и вы передаете этот объект по ссылке. Смысл, это тот же объект в памяти. Таким образом, ваши изменения будут соблюдены. См. Пример при отправке по ссылке для примера.
Типы значений не могут быть нулевыми, ссылочные типы могут
В значительной степени, как говорится, вы можете назначить null ссылочному типу, то есть назначенная вам переменная не может иметь никакого действительного объекта, назначенного ему. Однако в случае типов значений это невозможно. Тем не менее, вы можете использовать Nullable, чтобы ваш тип значения имел значение NULL, если это требование, но если это то, что вы рассматриваете, подумайте, действительно ли класс может быть лучшим подходом здесь, если он является вашим собственным тип.
Изменение значений в другом месте
public static void Main(string[] args)
{
var studentList = new List<Student>();
studentList.Add(new Student("Scott", "Nuke"));
studentList.Add(new Student("Vincent", "King"));
studentList.Add(new Student("Craig", "Bertt"));
// make a separate list to print out later
var printingList = studentList; // this is a new list object, but holding the same student objects inside it
// oops, we've noticed typos in the names, so we fix those
studentList[0].LastName = "Duke";
studentList[1].LastName = "Kong";
studentList[2].LastName = "Brett";
// okay, we now print the list
PrintPrintingList(printingList);
}
private static void PrintPrintingList(List<Student> students)
{
foreach (Student student in students)
{
Console.WriteLine(string.Format("{0} {1}", student.FirstName, student.LastName));
}
}
Вы заметите, что, несмотря на то, что список списков печати был сделан до исправления имен учеников после опечаток, метод PrintPrintingList все еще печатает исправленные имена:
Scott Duke
Vincent Kong
Craig Brett
Это связано с тем, что в обоих списках содержится список ссылок на те же ученики. SO изменение базового объекта-ученика распространяется на использование в любом списке.
Вот как выглядит класс ученика.
public class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Student(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
}
Передача по ссылке
Если вы хотите, чтобы примеры методов типов и ссылочных типов в правиле корректно работали, используйте ключевое слово ref в вашей сигнатуре метода для параметра, который вы хотите передать по ссылке, а также при вызове метода.
public static void Main(string[] args)
{
...
DoubleNumber(ref number); // calling code
Console.WriteLine(number); // outputs 8
...
}
public void DoubleNumber(ref int number)
{
number += number;
}
Выполнение этих изменений сделает обновление номера ожидаемым, что означает, что вывод консоли для числа будет 8.
Передача по ссылке с использованием ключевого слова ref.
Из документации :
В C # аргументы могут передаваться параметрам либо по значению, либо по ссылке. Передача по ссылке позволяет членам функции, методам, свойствам, индексаторам, операторам и конструкторам изменять значение параметров и сохранять это изменение в вызывающей среде. Чтобы передать параметр по ссылке, используйте ключевое слово
ref
илиout
.
Разница между ref
и out
заключается в том out
что передаваемый параметр должен быть назначен перед функцией ends.in контрастные параметры, переданные с ref
могут быть изменены или оставлены без изменений.
using System;
class Program
{
static void Main(string[] args)
{
int a = 20;
Console.WriteLine("Inside Main - Before Callee: a = {0}", a);
Callee(a);
Console.WriteLine("Inside Main - After Callee: a = {0}", a);
Console.WriteLine("Inside Main - Before CalleeRef: a = {0}", a);
CalleeRef(ref a);
Console.WriteLine("Inside Main - After CalleeRef: a = {0}", a);
Console.WriteLine("Inside Main - Before CalleeOut: a = {0}", a);
CalleeOut(out a);
Console.WriteLine("Inside Main - After CalleeOut: a = {0}", a);
Console.ReadLine();
}
static void Callee(int a)
{
a = 5;
Console.WriteLine("Inside Callee a : {0}", a);
}
static void CalleeRef(ref int a)
{
a = 6;
Console.WriteLine("Inside CalleeRef a : {0}", a);
}
static void CalleeOut(out int a)
{
a = 7;
Console.WriteLine("Inside CalleeOut a : {0}", a);
}
}
Выход :
Inside Main - Before Callee: a = 20
Inside Callee a : 5
Inside Main - After Callee: a = 20
Inside Main - Before CalleeRef: a = 20
Inside CalleeRef a : 6
Inside Main - After CalleeRef: a = 6
Inside Main - Before CalleeOut: a = 6
Inside CalleeOut a : 7
Inside Main - After CalleeOut: a = 7
присваивание
var a = new List<int>();
var b = a;
a.Add(5);
Console.WriteLine(a.Count); // prints 1
Console.WriteLine(b.Count); // prints 1 as well
Присвоение переменной List<int>
не создает копию List<int>
. Вместо этого он копирует ссылку на List<int>
. Мы называем типы, которые ведут себя так, как ссылочные типы .
Разница с параметрами метода ref и out
Существует два возможных способа передать тип значения по ссылке: ref
и out
. Разница заключается в том, что при прохождении его ref
значение должно быть инициализирован , но не при прохождении его out
. Использование out
гарантирует, что переменная имеет значение после вызова метода:
public void ByRef(ref int value)
{
Console.WriteLine(nameof(ByRef) + value);
value += 4;
Console.WriteLine(nameof(ByRef) + value);
}
public void ByOut(out int value)
{
value += 4 // CS0269: Use of unassigned out parameter `value'
Console.WriteLine(nameof(ByOut) + value); // CS0269: Use of unassigned out parameter `value'
value = 4;
Console.WriteLine(nameof(ByOut) + value);
}
public void TestOut()
{
int outValue1;
ByOut(out outValue1); // prints 4
int outValue2 = 10; // does not make any sense for out
ByOut(out outValue2); // prints 4
}
public void TestRef()
{
int refValue1;
ByRef(ref refValue1); // S0165 Use of unassigned local variable 'refValue'
int refValue2 = 0;
ByRef(ref refValue2); // prints 0 and 4
int refValue3 = 10;
ByRef(ref refValue3); // prints 10 and 14
}
Улов в том , что при использовании out
параметра must
быть инициализирован перед выходом из метода, поэтому следующий способ можно с ref
, но не с out
:
public void EmtyRef(bool condition, ref int value)
{
if (condition)
{
value += 10;
}
}
public void EmtyOut(bool condition, out int value)
{
if (condition)
{
value = 10;
}
} //CS0177: The out parameter 'value' must be assigned before control leaves the current method
Это связано с тем, что если condition
не выполняется, value
становится неназначенным.
параметры ref vs out
Код
class Program
{
static void Main(string[] args)
{
int a = 20;
Console.WriteLine("Inside Main - Before Callee: a = {0}", a);
Callee(a);
Console.WriteLine("Inside Main - After Callee: a = {0}", a);
Console.WriteLine();
Console.WriteLine("Inside Main - Before CalleeRef: a = {0}", a);
CalleeRef(ref a);
Console.WriteLine("Inside Main - After CalleeRef: a = {0}", a);
Console.WriteLine();
Console.WriteLine("Inside Main - Before CalleeOut: a = {0}", a);
CalleeOut(out a);
Console.WriteLine("Inside Main - After CalleeOut: a = {0}", a);
Console.ReadLine();
}
static void Callee(int a)
{
a += 5;
Console.WriteLine("Inside Callee a : {0}", a);
}
static void CalleeRef(ref int a)
{
a += 10;
Console.WriteLine("Inside CalleeRef a : {0}", a);
}
static void CalleeOut(out int a)
{
// can't use a+=15 since for this method 'a' is not intialized only declared in the method declaration
a = 25; //has to be initialized
Console.WriteLine("Inside CalleeOut a : {0}", a);
}
}
Выход
Inside Main - Before Callee: a = 20
Inside Callee a : 25
Inside Main - After Callee: a = 20
Inside Main - Before CalleeRef: a = 20
Inside CalleeRef a : 30
Inside Main - After CalleeRef: a = 30
Inside Main - Before CalleeOut: a = 30
Inside CalleeOut a : 25
Inside Main - After CalleeOut: a = 25