C #의 참조 유형


79

이 코드를 고려하십시오.

public class Program
{
    private static void Main(string[] args)
    {
        var person1 = new Person { Name = "Test" };
        Console.WriteLine(person1.Name);

        Person person2 = person1;
        person2.Name = "Shahrooz";
        Console.WriteLine(person1.Name); //Output: Shahrooz
        person2 = null;
        Console.WriteLine(person1.Name); //Output: Shahrooz
    }
}

public class Person
{
    public string Name { get; set; }
}

물론, 할당 할 때 person1까지 person2Name의 속성이 person2변경되면 Nameperson1도 변경됩니다. person1person2같은 참조를 갖는다.

person2 = null, person1변수도 null이 아닌 이유는 무엇 입니까?

답변:


183

둘 다 person하고 person2있습니다 참조 같은 객체. 그러나 이들은 다른 참조입니다. 따라서 실행 중일 때

person2 = null;

참조 만 변경 하고 person2참조 person및 해당 객체는 변경하지 않습니다.

이것을 설명하는 가장 좋은 방법은 단순화 된 그림이라고 생각합니다. 이전 상황은 다음과 같습니다 person2 = null.

널 할당 전

다음 은 null 할당 후의 그림입니다 .

여기에 이미지 설명 입력

보시다시피, 두 번째 그림에서는 person2아무것도 참조하지 않지만 (또는 null엄격히 말해서, 참조 없음과 참조 null는 다른 조건이므로 Rune FS의 주석 참조 ), person여전히 기존 개체를 참조합니다.


22
즉, person과 person2는 무언가를 가리키고 person2는 null로 설정하여 아무것도 가리 키지 않습니다.
rro

2
@ShahroozJefri는 ㇱ 기본적으로, person1그리고 person2주소를 포함 2 개 개의 다른 비즈니스 카드입니다. 당신이 경우에 person1 = person2다음 person1지금의 복사본입니다 person2. 그들은 여전히 다른 명함이지만, 둘 다 동일한 개체를 가리키는 동일한 주소를 포함 합니다. 개체 자체는 변경되지 않습니다.
Nolonar

person2두 번째 그림 의 화살표는 아무것도 참조하지 않기 때문에 제거 할 것 입니다. 이것은 매달린 참조가 아닙니다.
Sameer Singh

@SameerSingh, 완료, 응답 주셔서 감사합니다. 처음에는 똑같이 생각했지만 어떤 이유로 마음이 바뀌 었습니다.
Andrei

2
참조를 사용할 때 변수를 만들고 다른 포인터에 할당하면 Alias라는 것이 생성됩니다. 여기서 동일한 메모리 위치를 가리키는 변수 3 개를 가정 해 보겠습니다. 이러한 변수 중 하나를 무효화하면 해당 메모리 위치를 가리키는 변수 하나가 적습니다. 모든 변수가 NULL을 가리키면 GC는 해당 위치를 정리하여 새 변수를 할당합니다. 적어도 Java에서는 GC가 그런 방식으로 작동합니다 D :
NemesisDoom

55

고려 person1person2 포인터와 같은 저장 장치의 일부 위치. 첫 번째 단계에서는 person1저장소에서 객체의 주소 만 보유하고 나중에 person2는 저장소에서 객체 의 메모리 위치 주소를 보유합니다. 나중에는 할당 할 때 nullperson2, person1숙박 영향을받지 않습니다. 그것이 당신이 결과를 보는 이유입니다.

읽을 수 있습니다 : Joseph Albahari의 Value vs Reference Types

그러나 참조 유형을 사용하면 객체가 메모리에 생성 된 다음 포인터와 같은 별도의 참조를 통해 처리됩니다.

다음 다이어그램을 사용하여 동일한 개념을 묘사하려고합니다.

여기에 이미지 설명 입력

사람 유형의 새 개체를 만들고 person1참조 (포인터)가 저장소의 메모리 위치를 가리키고 있습니다.

여기에 이미지 설명 입력

person2저장소에서 동일한 것을 가리키는 새로운 참조 (포인터) 를 생성했습니다 .

여기에 이미지 설명 입력

을 통해 새로운 값으로 객체 속성 명칭 변경 person2을 모두 참조가 동일한 객체를 가리키는 이후 Console.WriteLine(person1.Name);출력한다 Shahrooz.

여기에 이미지 설명 입력

할당 한 후에 nullperson2참조, 그것은 아무것도를 가리키는되지만 person1여전히 객체에 대한 참조를 들고있다.

(마지막으로 메모리 관리에 대해서는 Eric Lippert의 The Stack Is An Implementation Detail, Part OneThe Stack Is An Implementation Detail, Part Two 를 참조해야합니다.)


3
"힙"에 대한 모든 참조는 관련없는 구현 세부 사항입니다. 나는 그것이 사실인지 확신하지 못합니다. 확실히 그럴 필요는 없습니다.
jmoreno

@jmoreno, 이것이 마지막 단락이었던 이유입니다. 실제로 저는 Joseph Albahari의 기사에서 스타일을 따랐습니다. 답변 상단에 링크되어 있습니다
Habib

4
@jmoreno, 지금까지이 답변에 관한 한 유형의 개체가 있음을 올바른 person힙과 참조에 저장됩니다 person1person2스택에있을 것이다. 그러나 나는 더 많은 구현 세부 사항에 동의합니다. 그러나 대답에 스택과 힙이 있으면 (albahari의 기사와 같이) IMO가 더 명확해질 것입니다.
Habib

1
@jmoreno 및 Habin : 일반적으로 힙으로 알려진 특정 스토리지 메커니즘의 사용 여부는 관련이 없으므로 사용을 스토리지 로 대체했습니다 .
Pieter Geerkens 2013-08-14

1
훌륭한 대답이지만 질문하는 질문에 대해서는 약간 이상입니다.
Razor

14

당신은 변했습니다 person2reference로null 했지만 참조 하고 person1있지 않습니다.

내 말은 우리가 보면 person2person1다음 할당을 모두 참조 동일한 개체 전에. 그런 다음을 할당 person2 = null하면 사람 2가 이제 다른 유형을 참조합니다. person2참조 된 개체를 삭제하지 않았습니다 .

설명하기 위해이 gif를 만들었습니다.

여기에 이미지 설명 입력


13

참조를 설정했기 때문에 null .

참조를로 설정 null하면 참조 자체는null 하는 객체 아닙니다.

0에서 오프셋을 유지하는 변수로 생각하십시오 person. 값 person2은 120이고 값은 120입니다 Person. 오프셋 120의 데이터는 객체입니다. 이렇게하면 :

person2 = null;

.. 당신은 효과적으로 말하고 person2 = 0;있습니다. 그러나 person여전히 값은 120입니다.


그래서 사람 2와 사람은 같은 참조가 아닙니다?

그들은 같은 객체를 참조합니다. 그러나 그들은 별개의 참조입니다. 이것은 copy-by-value의미론 으로 귀결됩니다. 참조 값을 복사하고 있습니다.
Simon Whitehead

4

모두 personperson2 포인트 같은 객체. 따라서 둘 중 하나의 이름을 변경하면 둘 다 변경됩니다 (메모리의 동일한 구조를 가리 키기 때문에).

사용자가 설정 한 때 person2null, 당신은 확인 person2이되지 않습니다 그래서, 널 포인터로 가리키는 과 같은 객체에 person더 이상. 그것은 그것을 파괴하기 위해 객체 자체에 아무것도하지 않을 것이며, person여전히 객체를 가리 키거나 참조하기 때문에 가비지 수집에 의해 죽지 않을 것입니다.

을 설정 person = null하고 객체에 대한 다른 참조가없는 경우 결국 가비지 수집기에 의해 제거됩니다.


2

person1person2동일한 메모리 어드레스를 가리킬. null이면 person2메모리 주소가 아닌 참조가 null이므로 person1해당 메모리 주소를 계속 참조하므로 그 이유입니다. 당신이를 변경하는 경우 Classs Person으로 Struct, 동작이 변경됩니다.


그들은 동일한 메모리 주소 (또는 해당 문제에 대한 임의의 주소)를 가리킬 수도 있고 가리 키지 않을 수도 있습니다. 중요한 것은 동일한 객체식별 한다는 것 입니다. 어떤 종류의 동시 가비지 수집기에서는 객체가 재배치되는 동안 일부 참조가 이전 주소를 보유하고 다른 참조가 새 주소를 식별 할 수 있습니다 [모든 참조가 업데이트 될 때까지 두 주소에 기록하는 코드를 차단해야 할 수 있음]. 주소를 비교하는 코드는 한 주소가 "현재"이고 다른 주소가 아닌지 확인해야합니다. 그렇다면 이전 주소와 관련된 "새"주소를 찾아야합니다.]
supercat

2

참조 유형을 개체 ID를 보유하는 것으로 생각하는 것이 가장 도움이됩니다. 클래스 유형의 변수가있는 경우 Car명령문 myCar = new Car();은 시스템에 새 자동차를 만들고 ID를보고하도록 요청합니다 (객체 # 57이라고 가정 해 보겠습니다). 그런 다음 "object # 57"을 변수에 넣습니다 myCar. 만약 하나 개 쓰기 Car2 = myCar;변수 Car2에 "개체 # 57"를 쓴다. 라고 쓰면 car2.Color = blue;시스템이 Car2 (예 : 객체 # 57)로 식별되는 자동차를 찾고 파란색으로 칠하도록 지시합니다.

객체 ID에서 직접 수행되는 유일한 작업은 새 객체 생성 및 ID 가져 오기, "빈"ID (예 : null) 가져 오기, 객체 ID를 저장할 수있는 변수 또는 저장 위치에 복사, 두 가지 여부 확인입니다. 개체 ID가 일치합니다 (동일한 개체 참조). 다른 모든 요청은 시스템에 ID가 참조하는 객체를 찾고 해당 객체에 대해 조치를 취하도록 요청합니다 (ID를 보유한 변수 또는 기타 엔티티에 영향을주지 않음).

.NET의 기존 구현에서 개체 변수는 가비지 수집 된 힙에 저장된 개체에 대한 포인터를 보유 할 가능성이 있지만 개체 참조와 다른 종류의 포인터간에 중요한 차이가 있기 때문에 구현 세부 사항은 도움이되지 않습니다. 포인터는 일반적으로 작업 할 수있을만큼 오래 머무를 위치를 나타내는 것으로 간주됩니다. 개체 참조는 그렇지 않습니다. 코드 조각은 주소 0x12345678에있는 개체에 대한 참조와 함께 SI 레지스터를로드하고 사용을 시작한 다음 가비지 수집기가 개체를 주소 0x23456789로 이동하는 동안 중단 될 수 있습니다. 그것은 재앙처럼 들리 겠지만, 쓰레기는 코드와 관련된 메타 데이터를 조사 할 것이고, 코드가 SI를 사용하여 그것이 사용하고 있던 객체의 주소를 보유하고 있다는 것을 관찰 할 것입니다 (예 : 0x12345678). 0x12345678에 있던 개체가 0x23456789로 이동되었는지 확인하고 SI를 업데이트하여 반환되기 전에 0x23456789를 유지합니다. 이 시나리오에서 SI에 저장된 숫자 값은 가비지 수집기에 의해 변경되었지만이동 전과 후에 동일한 개체 . 이동하기 전에 프로그램 시작 이후 생성 된 23,592 번째 개체를 참조하는 경우 나중에 계속 그렇게합니다. 흥미롭게도 .NET은 대부분의 개체에 대해 고유하고 변경할 수없는 식별자를 저장하지 않습니다. 프로그램 메모리의 두 스냅 샷이 주어지면 첫 번째 스냅 샷의 특정 개체가 두 번째 스냅 샷에 존재하는지 또는 모든 추적이 취소되고 새 개체가 생성되었는지 여부를 항상 알 수있는 것은 아닙니다. 모든 관찰 가능한 세부 사항.


1

person1과 person2는 힙의 동일한 Person 객체를 가리키는 스택의 두 개의 개별 참조입니다.

참조 중 하나를 삭제하면 스택에서 제거되고 더 이상 힙의 Person 개체를 가리 키지 않습니다. 다른 참조는 여전히 힙의 기존 Person 개체를 가리키고 있습니다.

Person 개체에 대한 모든 참조가 제거되면 결국 가비지 수집기가 메모리에서 개체를 제거합니다.


1

당신은 당신이 PERSON2는 참고 사람의 단지 사본을 같이하여 Person2 = NULL은 아무 효과가 없습니다 할당하고 우리가 한 그러나 경우, 그 사실은 동일한 메모리 위치를 가리키는 모든 객체에 대한 참조를 복사 입력에 대한 참조를 만들 때 사본을 삭제 참조 .


1

으로 변경하여 값 의미를 얻을 수 있습니다 struct.

public class Program
{
    static void Main()
    {
        var person1 = new Person { Name = "Test" };
        Console.WriteLine(person1.Name);

        Person person2 = person1;
        person2.Name = "Shahrooz";
        Console.WriteLine(person1.Name);//Output:Test
        Console.WriteLine(person2.Name);//Output:Shahrooz
        person2 = new Person{Name = "Test2"};
        Console.WriteLine(person2.Name);//Output:Test2

    }
}
public struct Person
{
    public string Name { get; set; }
}

0
public class Program
{
    private static void Main(string[] args)
    {
        var person = new Person {Name = "Test"};
        Console.WriteLine(person.Name);

        Person person2 = person;
        person2.Name = "Shahrooz";
        Console.WriteLine(person.Name);//Output:Shahrooz
        // Here you are just erasing a copy to reference not the object created.
        // Single memory allocation in case of reference type and  parameter
         // are passed as a copy of reference type .   
        person2 = null;
        Console.WriteLine(person.Name);//Output:Shahrooz

    }
}
public class Person
{
    public string Name { get; set; }
}

@doctorlove는 실제로 주석을 잊어 버렸습니다 .- (, Well 참조 유형의 경우 하나의 메모리 공간 만 할당되고 Person 2 또는 Person 1 ...은 메모리 위치를 가리키는 참조 유형의 사본입니다. 참조 유형의 사본 지우기 아무 영향을 미치지 않습니다.
수 라즈 싱에게

@doctorlove 당신의 제안에 감사드립니다, 나는 그것을 명심할 것입니다.
Suraj Singh

0

먼저에 대한 참조를 person1에 복사합니다 person2. 지금 person1person2객체의 값 수정 수단 동일한 목적 (즉, 변화하는 참조 Name두 변수 하에서 관찰 될 수 속성). 그런 다음 null을 할당 할 때 방금 할당 한 참조를 제거합니다 person2. person1지금 만 할당됩니다 . 참조 자체는 변경 되지 않습니다 .

당신이 참조로 인수를 허용하는 기능이 있다면, 당신은 통과 할 수 reference to person1 참조 및 기준 자체를 변경 할 수있을 것입니다 :

public class Program
{
    private static void Main(string[] args)
    {
        var person1 = new Person { Name = "Test" };
        Console.WriteLine(person1.Name);

        PersonNullifier.NullifyPerson(ref person1);
        Console.WriteLine(person1); //Output: null
    }
}


class PersonNullifier
{
    public static void NullifyPerson( ref Person p ) {
        p = null;
    }
}

class  Person {
    public string Name{get;set;}
}

0

속담:

person2.Name = "Shahrooz";

참조를 따르고 person2참조가 발생하는 객체를 "변형"(변경)합니다. 자체 참조합니다 ( person2 ) 변경되지 않는다; 우리는 여전히 동일한 "주소"에서 동일한 인스턴스를 참조합니다.

에 할당 person2:

person2 = null;

참조를 변경합니다 . 개체가 변경되지 않습니다. 이 경우 참조 화살표는 한 개체에서 "없음"으로 "이동"됩니다 null. 그러나 같은 할당 person2 = new Person();은 참조 만 변경합니다. 개체가 변경되지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.