C # 문자열 참조 유형?


164

C #의 "string"이 참조 유형이라는 것을 알고 있습니다. 이것은 MSDN에 있습니다. 그러나이 코드는 다음과 같이 작동하지 않습니다.

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(string test)
    {
        test = "after passing";
    }
}

문자열을 매개 변수로 전달하고 참조 유형이므로 출력은 "전달 전" "전달 후"여야합니다. 두 번째 출력 명령문은 TestI 메소드에서 텍스트가 변경되었음을 인식해야합니다. 그러나, 나는 "통과하기 전에" "통과하기 전에"가 ref가 아닌 값으로 전달되는 것처럼 보입니다. 나는 문자열이 변경 불가능하다는 것을 알고 있지만 여기서 무슨 일이 일어나고 있는지 설명하지 못합니다. 내가 무엇을 놓치고 있습니까? 감사.


아래 Jon이 참조한 기사를 참조하십시오. 언급 한 동작은 C ++ 포인터로도 재현 할 수 있습니다.
Sesh

MSDN 에서도 매우 좋은 설명입니다 .
Dimi_Pel

답변:


211

문자열에 대한 참조는 값으로 전달됩니다. 값으로 참조를 전달하는 것과 참조로 객체를 전달하는 것에는 큰 차이가 있습니다. 불행히도 "참조"라는 단어는 두 경우 모두에 사용됩니다.

당신이 경우에 문자열 참조 통과 하여 참조를 예상대로, 그것은 작동합니다 :

using System;

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(ref test);
        Console.WriteLine(test);
    }

    public static void TestI(ref string test)
    {
        test = "after passing";
    }
}

이제 참조가 참조하는 객체를 변경하는 것과 다른 객체를 참조 할 수 있도록 변수 (예 : 매개 변수)를 변경하는 것을 구별해야합니다. 문자열은 변경할 수 없기 때문에 문자열을 변경할 수 없지만 StringBuilder대신 문자열로 보여줄 수 있습니다 .

using System;
using System.Text;

class Test
{
    public static void Main()
    {
        StringBuilder test = new StringBuilder();
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(StringBuilder test)
    {
        // Note that we're not changing the value
        // of the "test" parameter - we're changing
        // the data in the object it's referring to
        test.Append("changing");
    }
}

자세한 내용은 매개 변수 전달에 대한 내 기사 를 참조하십시오.


2
ref 수정자를 사용하면 비 참조 유형에도 적용됩니다. 즉, 둘 다 상당히 분리 된 개념입니다.
eglasius

2
@Jon Skeet은 기사에서 주석을 좋아했습니다. 당신은 referenced당신의 대답으로해야합니다
Nithish Inpursuit Ofhappiness

36

질문에 대답해야 할 경우 : String은 참조 유형이며 참조로 동작합니다. 실제 문자열이 아닌 참조를 보유한 매개 변수를 전달합니다. 문제는 기능에 있습니다.

public static void TestI(string test)
{
    test = "after passing";
}

매개 변수 test는 문자열에 대한 참조를 보유하지만 사본입니다. 문자열을 가리키는 두 개의 변수가 있습니다. 그리고 문자열을 사용한 작업은 실제로 새 객체를 생성하기 때문에 로컬 복사본이 새 문자열을 가리 키도록 만듭니다. 그러나 원래 test변수는 변경되지 않습니다.

변수 ref의 값을 전달하지 않고 test단지 참조를 전달 하기 때문에 함수 선언과 호출 작업 에 넣을 제안 솔루션 . 따라서 함수 내부의 변경 사항은 원래 변수를 반영합니다.

마지막에 반복하고 싶습니다 : String은 참조 유형이지만 변경할 수 없기 때문에 줄이 test = "after passing";실제로 새 객체를 만들고 변수의 사본 test이 새 문자열을 가리 키도록 변경됩니다.


25

다른 사람들이 언급했듯이 String.NET 의 유형은 변경할 수 없으며 참조가 값으로 전달됩니다.

이 코드가 실행 되 자마자 원래 코드에서 :

test = "after passing";

다음 test더 이상 언급하지 원래 오브젝트로. String 개체를 만들고 test관리되는 힙에서 해당 개체를 참조하도록 할당 했습니다.

눈에 띄는 공식적인 생성자가 없기 때문에 많은 사람들이 여기에 걸려 넘어 진다고 생각합니다. 이 경우 String유형이 구성 방식에 대한 언어 지원이 있기 때문에 배후에서 발생합니다 .

따라서 변경 사항 testTestI(string)메소드 범위 밖에서 보이지 않는 이유가 있습니다. 값으로 참조를 전달했으며 이제 해당 값이 변경되었습니다! 그러나 String참조가 참조로 전달 된 경우 참조가 변경되면 TestI(string)메소드 의 범위를 벗어난 참조를 보게됩니다 .

어느 심판 또는 밖으로 키워드는이 경우에 필요하다. out키워드가이 특정 상황에 약간 더 적합 하다고 생각합니다 .

class Program
{
    static void Main(string[] args)
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(out test);
        Console.WriteLine(test);
        Console.ReadLine();
    }

    public static void TestI(out string test)
    {
        test = "after passing";
    }
}

ref = 외부 함수 초기화, out = 함수 내부 초기화 또는 다른 말로; 심판은 양방향이며, 아웃은 아웃 전용입니다. 반드시 참조를 사용해야합니다.
Paul Zahra

@PaulZahra : out코드를 컴파일하려면 메소드 내에 할당해야합니다. ref그러한 요구 사항이 없습니다. 또한 out매개 변수는 메서드 외부에서 초기화됩니다.이 답변의 코드는 반례입니다.
Derek W

명확히해야합니다- out매개 변수 메소드 외부에서 초기화 할 수 있지만 반드시 그럴 필요는 없습니다. 이 경우 out매개 변수 를 초기화하여 string.NET 의 유형 특성에 대한 요점을 보여 주려고합니다.
Derek W

9

실제로 그것은 그 문제에 대한 모든 객체에 대해 동일했을 것입니다. 즉 참조 유형이며 참조로 전달하는 것은 C #에서 두 가지입니다.

이것은 작동하지만 유형에 관계없이 적용됩니다.

public static void TestI(ref string test)

또한 문자열이 참조 유형이며 특수 유형이기도합니다. 변경 불가능하도록 설계되었으므로 모든 메소드가 인스턴스를 수정하지 않습니다 (새 인스턴스를 리턴 함). 또한 성능을 위해 몇 가지 추가 사항이 있습니다.


7

다음은 값 유형, 값별 전달, 참조 유형 및 참조 별 전달의 차이점을 고려하는 좋은 방법입니다.

변수는 컨테이너입니다.

값 유형 변수에는 인스턴스가 포함됩니다. 참조 형 변수는 다른 곳에 저장된 인스턴스에 대한 포인터를 포함합니다.

값 유형 변수를 수정하면 포함 된 인스턴스가 변경됩니다. 참조 유형 변수를 수정하면 그것이 가리키는 인스턴스가 변경됩니다.

별도의 참조 유형 변수는 동일한 인스턴스를 가리킬 수 있습니다. 따라서 동일한 인스턴스는이를 가리키는 모든 변수를 통해 변경 될 수 있습니다.

값으로 전달되는 인수는 컨텐츠의 새 사본이있는 새 컨테이너입니다. 참조에 의해 전달 된 인수는 원래 내용이있는 원래 컨테이너입니다.

값 유형 인수가 값별로 전달되는 경우 : 컨테이너가 고유하기 때문에 인수 내용을 다시 할당해도 범위를 벗어나지 않습니다. 인스턴스가 독립 사본이므로 인수를 수정해도 범위를 벗어나지 않습니다.

참조 유형 인수가 값으로 전달되는 경우 : 컨테이너가 고유하기 때문에 인수 내용을 다시 할당해도 범위를 벗어나지 않습니다. 복사 된 포인터가 공유 인스턴스를 가리 키기 때문에 인수의 내용을 수정하면 외부 범위에 영향을줍니다.

인수가 참조로 전달되는 경우 : 컨테이너가 공유되므로 인수의 내용을 다시 할당하면 외부 범위에 영향을줍니다. 인수의 내용을 수정하면 내용이 공유되므로 외부 범위에 영향을줍니다.

결론적으로:

문자열 변수는 참조 유형 변수입니다. 따라서 다른 곳에 저장된 인스턴스에 대한 포인터를 포함합니다. 값으로 전달되면 포인터가 복사되므로 문자열 인수를 수정하면 공유 인스턴스에 영향을 미칩니다. 그러나 문자열 인스턴스에는 변경 가능한 속성이 없으므로 문자열 인수는 어쨌든 수정할 수 없습니다. 참조로 전달되면 포인터의 컨테이너가 공유되므로 재 할당은 여전히 ​​외부 범위에 영향을 미칩니다.


6

" 그림은 천 단어의 가치가있다 ".

여기 간단한 예가 있습니다. 귀하의 경우와 비슷합니다.

string s1 = "abc";
string s2 = s1;
s1 = "def";
Console.WriteLine(s2);
// Output: abc

이것이 일어난 일입니다.

여기에 이미지 설명을 입력하십시오

  • 1 호선과 2 : s1s2변수는 동일하게 참조하는 "abc"문자열 객체입니다.
  • 행 3 : 때문에 문자열은 불변 이므로 "abc"문자열 객체는 (자체를 수정하지 않는 "def"),하지만 새로운 "def"다음 문자열 객체 대신 작성하고, s1그것을 참조.
  • 4 행 : s2여전히 "abc"문자열 오브젝트에 대한 참조 이므로 출력입니다.

5

위의 답변이 도움이되었으므로 매개 변수가 참조 유형 인 경우에도 ref 키워드없이 매개 변수를 전달할 때 발생하는 상황을 명확하게 보여주는 예제를 추가하고 싶습니다.

MyClass c = new MyClass(); c.MyProperty = "foo";

CNull(c); // only a copy of the reference is sent 
Console.WriteLine(c.MyProperty); // still foo, we only made the copy null
CPropertyChange(c); 
Console.WriteLine(c.MyProperty); // bar


private void CNull(MyClass c2)
        {          
            c2 = null;
        }
private void CPropertyChange(MyClass c2) 
        {
            c2.MyProperty = "bar"; // c2 is a copy, but it refers to the same object that c does (on heap) and modified property would appear on c.MyProperty as well.
        }

1
이 설명은 저에게 가장 효과적이었습니다. 따라서 기본적으로 변수 자체가 ref 키워드 (또는 out)를 사용하지 않는 한 값 또는 참조 유형이라는 사실에도 불구하고 기본적으로 모든 값을 값으로 전달합니다. 일반적으로 객체를 null 또는 객체가 전달 된 메소드 내부의 다른 인스턴스로 설정하지 않고 속성을 설정하거나 메소드를 호출하기 때문에 일상적인 코딩에서는 눈에 띄지 않습니다. "문자열"의 경우, 새로운 인스턴스로 설정하는 것은 항상 발생하지만 새로 작성하는 것은 보이지 않으며 훈련되지 않은 눈에 대한 잘못된 해석을 제공합니다. 틀린 경우 수정하십시오.
Ε Г И І И О

3

궁금한 점이 있고 대화를 완료하려면 : 예, 문자열은 참조 유형입니다 .

unsafe
{
     string a = "Test";
     string b = a;
     fixed (char* p = a)
     {
          p[0] = 'B';
     }
     Console.WriteLine(a); // output: "Best"
     Console.WriteLine(b); // output: "Best"
}

그러나이 변경은 안전하지 않은 블록 에서만 작동합니다 ! 문자열은 변경할 수 없기 때문에 (MSDN에서) :

문자열 객체의 내용은 객체를 만든 후에는 변경할 수 없지만 구문을 사용하면 마치 마치 그렇게 할 수 있습니다. 예를 들어,이 코드를 작성할 때 컴파일러는 실제로 새로운 문자 시퀀스를 보유하기 위해 새 문자열 객체를 작성하고 해당 새 객체는 b에 할당됩니다. 그런 다음 문자열 "h"는 가비지 수집에 적합합니다.

string b = "h";  
b += "ello";  

그리고 다음을 명심하십시오.

문자열은 참조 유형이지만 항등 연산자 ( ==!=)는 참조가 아닌 문자열 객체의 값을 비교하기 위해 정의됩니다.


0

귀하의 코드는 다음과 유사하다고 생각하며 여기에없는 것과 같은 이유로 값이 변경 될 것으로 기 대해서는 안됩니다.

 public static void Main()
 {
     StringWrapper testVariable = new StringWrapper("before passing");
     Console.WriteLine(testVariable);
     TestI(testVariable);
     Console.WriteLine(testVariable);
 }

 public static void TestI(StringWrapper testParameter)
 {
     testParameter = new StringWrapper("after passing");

     // this will change the object that testParameter is pointing/referring
     // to but it doesn't change testVariable unless you use a reference
     // parameter as indicated in other answers
 }

-1

시험:


public static void TestI(ref string test)
    {
        test = "after passing";
    }

3
귀하의 답변에는 코드 이상의 것이 포함되어야합니다. 왜 작동하는지에 대한 설명도 포함해야합니다.
찰스 콜드웰
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.