ref를 사용하는 경우 및 C #에서 필요하지 않은 경우


104

프로그램의 메모리 상태 인 개체가 있고 상태를 수정하기 위해 개체를 전달하는 다른 작업자 함수도 있습니다. 작업자 함수에 ref로 전달했습니다. 그러나 나는 다음과 같은 기능을 발견했습니다.

byte[] received_s = new byte[2048];
IPEndPoint tmpIpEndPoint = new IPEndPoint(IPAddress.Any, UdpPort_msg);
EndPoint remoteEP = (tmpIpEndPoint);

int sz = soUdp_msg.ReceiveFrom(received_s, ref remoteEP); 

received_s와 둘 다 remoteEP함수에서 물건을 반환 하기 때문에 혼란 스럽습니다 . 이유는 무엇입니까 remoteEP필요성 A가 refreceived_s하지 않는 이유는 무엇입니까?

나는 또한 AC 프로그래머이므로 포인터를 머리에서 꺼내는 데 문제가 있습니다.

편집 : C #의 개체가 내부 개체에 대한 포인터 인 것 같습니다. 따라서 객체를 함수에 전달할 때 포인터를 통해 객체 내용을 수정할 수 있으며 함수에 전달되는 유일한 것은 객체에 대한 포인터이므로 객체 자체가 복사되지 않습니다. 이중 포인터와 같은 함수에서 전환하거나 새 개체를 만들려면 ref 또는 out을 사용합니다.

답변:


217

짧은 대답 : 인수 전달에 대한 기사를 읽으십시오 .

긴 대답 : 참조 유형 매개 변수가 값으로 전달되면 객체의 사본이 아닌 참조 만 전달됩니다 . 이것은 C 또는 C ++에서 포인터 (값으로)를 전달하는 것과 같습니다. 파라미터 자체의 값을 변경하면 호출자 본 이에 레퍼런스 포인트 객체로 변경되지 않습니다 알.

매개 변수 (모든 종류)가 참조 로 전달되면 매개 변수 대한 모든 변경 사항이 호출자 에게 표시됨 을 의미합니다. 매개 변수에 대한 변경 사항 은 변수에 대한 변경 사항입니다.

이 기사는 물론이 모든 것을 더 자세히 설명합니다. :)

유용한 답변 : ref / out을 사용할 필요가 거의 없습니다 . 이는 기본적으로 다른 반환 값을 얻는 방법이며, 메서드가 너무 많은 작업을 시도하고 있음을 의미하기 때문에 일반적으로 피해야합니다. 항상 그런 것은 아니지만 ( TryParse등은를 합리적으로 사용하는 표준 예입니다 out) ref / out 사용은 상대적으로 드 물어야합니다.


38
짧은 답변과 긴 답변이 뒤섞인 것 같습니다. 그것은 큰 기사입니다!
Outlaw Programmer

23
@Outlaw : 예,하지만 기사를 읽으라는 지시자 인 짧은 대답 자체는 6 단어 밖에되지 않습니다. :)
Jon Skeet

27
참고! :)
gonzobrains dec.

5
@Liam 당신처럼 ref를 사용하는 것은 당신에게 더 명확하게 보일 수 있지만 실제로는 다른 프로그래머들 (어쨌든 그 키워드가 무엇을하는지 아는 사람들)에게는 혼란 스러울 수 있습니다. 왜냐하면 당신은 본질적으로 잠재적 인 호출자들에게 "나는 당신 이 변수 를 수정할 수 있습니다. 호출 메서드에 사용됩니다. 즉 , 다른 개체 (또는 가능한 경우 null)에 다시 할당 하므로 계속 사용하지 않거나 작업이 끝나면 유효성을 검사하십시오. " 이는 매우 강력하고 "이 객체는 수정할 수 있음"과는 완전히 다릅니다 . 객체 참조를 매개 변수로 전달할 때마다 항상 그렇습니다.
mbargiel 2014 년

1
@ M.Mimpen : C # 7 (희망) 수if (int.TryParse(text, out int value)) { ... use value here ... }
존 소총

26

non-ref 매개 변수를 포인터로, ref 매개 변수를 이중 포인터로 생각하십시오. 이것이 가장 큰 도움이되었습니다.

ref로 값을 전달해서는 안됩니다. Interop 문제가 아니었다면 .Net 팀은 원래 사양에 포함하지 않았을 것입니다. ref 매개 변수가 해결하는 대부분의 문제를 처리하는 OO 방법은 다음과 같습니다.

여러 반환 값의 경우

  • 여러 반환 값을 나타내는 구조체 만들기

메서드 호출의 결과로 메서드에서 변경되는 기본 요소의 경우 (메서드에는 기본 매개 변수에 대한 부작용이 있음)

  • 개체의 메서드를 인스턴스 메서드로 구현하고 메서드 호출의 일부로 개체의 상태 (매개 변수가 아님)를 조작합니다.
  • 다중 반환 값 솔루션을 사용하고 반환 값을 상태에 병합하십시오.
  • 메서드로 조작 할 수있는 상태를 포함하는 개체를 만들고 해당 개체를 기본 요소 자체가 아닌 매개 변수로 전달합니다.

8
좋은 신. 이해하려면이 20 배를 읽어야합니다. 단순한 일을하기위한 추가 작업처럼 들립니다.
PositiveGuy

.NET 프레임 워크는 # 1 규칙을 따르지 않습니다. ( '여러 반환 값의 경우 구조체 생성'). 예를 들어 보자 IPAddress.TryParse(string, out IPAddress).
Swen Kooij

@SwenKooij 당신이 맞아요. 나는 그들이 매개 변수를 사용하는 대부분의 장소에서 (a) Win32 API를 래핑하거나 (b) 초기였으며 C ++ 프로그래머가 결정을 내리고 있다고 가정합니다.
Michael Meadows

@SwenKooij 나는이 대답이 몇 년 늦었 음을 알고 있지만 그렇습니다. 우리는 TryParse에 익숙하지만 그것이 좋다는 의미는 아닙니다. 보다는 경우 더 좋았을 것 if (int.TryParse("123", out var theInt) { /* use theInt */ }우리가 가진 var candidate = int.TrialParse("123"); if (candidate.Parsed) { /* do something with candidate.Value */ }그것은 더 많은 코드이지만, 훨씬 더 일관성 C # 언어 디자인입니다.
Michael Meadows

9

전체 C # 앱을 작성하고 ref로 객체 / 구조체를 전달하지 않을 수 있습니다.

나에게 이렇게 말한 교수가 있었다.

refs를 사용하는 유일한 장소는 다음 중 하나입니다.

  1. 큰 개체 (즉, 개체 / 구조체 내부에 여러 수준의 개체 / 구조체가 있음)를 전달하고 싶은 경우 복사하는 데 비용이 많이 들고
  2. 프레임 워크, Windows API 또는이를 필요로하는 기타 API를 호출하고 있습니다.

할 수 있다고해서하지 마십시오. 매개 변수의 값을 변경하기 시작하고주의를 기울이지 않으면 몇 가지 불쾌한 버그로 인해 엉덩이에 약간 걸릴 수 있습니다.

나는 그의 조언에 동의하며, 학교 이후 5 년이 지난 지금까지 프레임 워크 나 Windows API를 호출하는 것 외에는 필요가 없었습니다.


3
"Swap"을 구현할 계획이라면 ref로 전달하는 것이 도움이 될 수 있습니다.
Brian

@Chris는 작은 물체에 ref 키워드를 사용하면 문제가 될까요?
ManirajSS

@TechnikEmpire "그러나 호출 된 함수의 범위 내에서 개체에 대한 변경은 호출자에게 다시 반영되지 않습니다." 그건 틀렸어요. Person을에 전달 SetName(person, "John Doe")하면 name 속성이 변경되고 해당 변경 사항이 호출자에게 반영됩니다.
M. Mimpen

@ M.Mimpen 댓글이 삭제되었습니다. 나는 그 시점에서 간신히 C #을 선택했고 분명히 내 * $$에 대해 이야기하고 있었다. 관심을 가져 주셔서 감사합니다.

@Chris- "큰"개체를 전달하는 것이 비싸지 않다고 확신합니다. 값으로 전달하면 여전히 단일 포인터를 전달하고 있습니까?
Ian

3

received_s는 배열이므로 해당 배열에 대한 포인터를 전달합니다. 이 함수는 기본 위치 나 포인터를 변경하지 않고 기존 데이터를 제자리에서 조작합니다. ref 키워드는 실제 포인터를 위치에 전달하고 해당 포인터를 외부 함수에서 업데이트하므로 외부 함수의 값이 변경됨을 나타냅니다.

예를 들어, 바이트 배열은 이전과 이후의 동일한 메모리에 대한 포인터이며 메모리가 방금 업데이트되었습니다.

Endpoint 참조는 실제로 외부 함수의 Endpoint에 대한 포인터를 함수 내부에서 생성 된 새 인스턴스로 업데이트합니다.


3

참조를 참조로 포인터를 전달한다는 의미로 참조를 생각하십시오. ref를 사용하지 않으면 값으로 포인터를 전달한다는 의미입니다.

더 나은 방법은 방금 말한 내용을 무시하고 (특히 값 유형에서 오해의 소지가있을 수 있음) This MSDN page를 읽는 것 입니다.


사실, 사실이 아닙니다. 적어도 두 번째 부분. ref 사용 여부에 관계없이 모든 참조 유형은 항상 참조로 전달됩니다.
Erik Funkenbusch

사실, 더 깊이 생각해 보면 제대로 나오지 않았습니다. 참조는 실제로 ref 유형이없는 값으로 전달됩니다. 즉, 참조가 가리키는 값을 변경하면 원래 데이터가 변경되지만 참조 자체를 변경해도 원래 참조는 변경되지 않습니다.
Erik Funkenbusch

2
참조가 아닌 참조 유형은 참조로 전달되지 않습니다. 참조 유형에 대한 참조는 값으로 전달됩니다. 그러나 참조를 참조되는 무언가에 대한 포인터로 생각하고 싶다면 내가 말한 내용이 의미가 있습니다 (그러나 그렇게 생각하면 오해의 소지가있을 수 있습니다). 그러므로 나의 경고.
Brian


0

내 이해는 Object 클래스에서 파생 된 모든 객체는 포인터로 전달되는 반면 일반 유형 (int, struct)은 포인터로 전달되지 않고 ref가 필요하다는 것입니다. 나는 문자열에 대해 확신하지 못합니다 (최종적으로 Object 클래스에서 파생됩니까?)


이것은 약간의 설명을 사용할 수 있습니다. 가치와 기준의 차이점은 무엇입니까? 매개 변수에 ref 키워드를 사용하는 것과 관련이있는 이유는 무엇입니까?
oɔɯǝɹ

.net에서는 포인터, 유형 매개 변수 및 인터페이스를 제외한 모든 것이 Object에서 파생됩니다. "일반"유형 (정확히 "값 유형"이라고 함)도 객체에서 상속된다는 점을 이해하는 것이 중요합니다. 당신이 맞습니다 : 값 유형 (기본값 당)은 값으로 전달되는 반면 참조 유형은 참조로 전달됩니다. 새 값을 반환하는 대신 값 유형을 수정하려면 (메서드 내부의 참조 유형처럼 처리) ref 키워드를 사용해야합니다. 그러나 나쁜 스타일이며, 당신은하지 말아야 당신은 절대적으로 당신이 가지고 있지 않는 :)에
buddybubble

1
질문에 답하기 위해 : 문자열은
객체

0

Jon Skeet의 전체 답변과 다른 답변에 동의하지만를 사용하는 사용 사례가 ref있으며 이는 성능 최적화를 강화하는 것입니다. 성능 프로파일 링 중에 메서드의 반환 값을 설정하면 성능에 약간의 영향을주는 반면 ref반환 값이 해당 매개 변수에 채워지는 인수로 사용 하면이 약간의 병목 현상이 제거되는 것으로 나타났습니다.

이것은 최적화 노력이 극도의 수준으로 진행되어 가독성과 테스트 가능성 및 유지 관리 가능성을 희생하여 밀리 초 또는 스플릿 밀리 초를 절약하는 경우에만 유용합니다.


-1

Ground zero rule 먼저, Primitive는 관련된 TYPES의 맥락에서 value (stack)와 Non-Primitive by reference (Heap)로 전달됩니다.

관련된 매개 변수는 기본적으로 Value로 전달됩니다. 세부 사항을 설명하는 좋은 게시물. http://yoda.arachsys.com/csharp/parameters.html

Student myStudent = new Student {Name="A",RollNo=1};

ChangeName(myStudent);

static void ChangeName(Student s1)
{
  s1.Name = "Z"; // myStudent.Name will also change from A to Z
                // {AS s1 and myStudent both refers to same Heap(Memory)
                //Student being the non-Primitive type
}

ChangeNameVersion2(ref myStudent);
static void ChangeNameVersion2(ref Student s1)
{
  s1.Name = "Z"; // Not any difference {same as **ChangeName**}
}

static void ChangeNameVersion3(ref Student s1)
{
    s1 = new Student{Name="Champ"};

    // reference(myStudent) will also point toward this new Object having new memory
    // previous mystudent memory will be released as it is not pointed by any object
}

(경고와 함께) 기본이 아닌 유형은 포인터에 불과 하며 ref로 전달할 때 Double Pointer를 전달한다고 말할 수 있습니다.


모든 매개 변수는 기본적으로 C #에서 값으로 전달됩니다. 모든 매개 변수는 참조로 전달할 수 있습니다. 참조 유형의 경우 전달 된 값 (참조 또는 값) 자체가 참조입니다. 그것은 그것이 전달되는 방법과 완전히 독립적입니다.
Servy

@Servy에 동의하십시오! ""참조 "또는"값 "이라는 단어를들을 때 우리는 매개 변수가 참조인지 값 매개 변수인지, 또는 관련된 유형이 참조인지 값 유형인지를 의미하는지에 대해 매우 명확해야합니다. 내 쪽에서 혼란 스러울 때 그라운드 제로 규칙을 먼저 말했을 때 Primitives는 value (stack) 및 Non-Primitive by reference (Heap)에 의해 전달됩니다 . , 모든 매개 변수는 기본적으로 C #에서 값으로 전달됩니다.
Surender Singh Malik 2014

매개 변수가 참조 또는 값 매개 변수라고 말하는 것은 표준 용어가 아니며 의미하는 바가 매우 모호합니다. 매개 변수 유형은 참조 유형 또는 값 유형일 수 있습니다. 모든 매개 변수는 값 또는 참조로 전달할 수 있습니다. 이는 주어진 매개 변수에 대한 직교 개념입니다. 귀하의 게시물이이를 잘못 설명하고 있습니다.
Servy

1
참조 또는 값으로 매개 변수를 전달합니다. "참조 별"및 "값별"이라는 용어는 유형이 값 유형인지 참조 유형인지를 설명하는 데 사용되지 않습니다.
Servy

1
아니, 지각의 문제가 아닙니다 . 당신이 사용하는 경우 잘못된 용어를 뭔가를 참조하는 그 문이된다 잘못 . 올바른 문장이 개념을 참조하기 위해 올바른 용어를 사용하는 것이 중요합니다.
Servy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.