C #에서 대리자 사용


79

C # 언어 및 .NET 프레임 워크에서 대리자를 이해하는 데 도움을 줄 수 있습니까? 일부 코드를 확인하려고했는데 내가받은 결과가 예상치 못한 결과라는 것을 알았습니다. 여기있어:

class Program
{
    public static int I = 0;

    static Func<string> del = new Func<string>(I.ToString);

    static void Main(string[] args)
    {
        I = 10;
        Console.WriteLine("{0}", del());
    }
}

대답은 0 이었지만 10이 아니 었습니다. 이유가 무엇입니까?


12
@Rotem : 아니, 그는하지 않았다.
Daniel Hilgarth

3
@Rotem-위임 선언입니다. 추가 ()호출 할 것이다 ToString.
Oded

1
죄송합니다, 결코 사용하지 Func들이었다 추측 :
로템

2
좋은 질문에 +1, 잘 부탁드립니다. 단순 해 보이는 질문이 언어 / 플랫폼의 잘 이해되지 않은 영역을 강조 할 수있는 방법에 대한 좋은 예입니다.
Martin

5
(유니 캐스트) 대리자 인스턴스는 인스턴스 메서드 또는 메서드를 가리킬 수 있습니다 static. 인스턴스 메서드를 나타낼 때 대리자는 메서드를 호출 할 "대상"개체와 메서드 정보를 모두 보유 합니다 . 그래서 당신이 말할 때 del = I.ToString;, 여기에있는 (불변 값 유형) del객체 I를 보유합니다 Int32. 당신은 익명 함수를 사용하는 경우 del = () => I.ToString();, 컴파일러는 메소드를 생성 static string xxx() { return I.ToString(); }하고 del객체가 생성 방법을 보유하고 있습니다.
Jeppe Stig Nielsen

답변:


79

그 이유는 다음과 같습니다.

대리자를 선언하는 ToString방식은 정적 int 인스턴스 의 메서드를 직접 가리 킵니다 . 생성 당시 캡처됩니다.

flindeberg가 아래 주석에서 지적했듯이 각 대리자는 대상에서 실행할 대상과 메서드를 가지고 있습니다.

이 경우 실행될 ToString메서드 는 분명히 메서드입니다. 흥미로운 부분은 메서드가 실행되는 인스턴스입니다 I. 생성 당시의 인스턴스입니다 . 즉, 대리자가 I사용할 인스턴스를 가져 오는 데 사용하지 않지만 인스턴스 자체에 대한 참조를 저장합니다.

나중에 I다른 값으로 변경 하여 기본적으로 새 인스턴스를 할당합니다. 이것은 델리게이트에서 캡처 된 인스턴스를 마술처럼 변경하지 않습니다. 왜 그래야합니까?

예상 한 결과를 얻으려면 대리자를 다음과 같이 변경해야합니다.

static Func<string> del = new Func<string>(() => I.ToString());

이와 같이 대리자는 대리자를 실행할 ToString때 현재 I에서 실행되는 익명 메서드를 가리 킵니다 .

이 경우 실행할 메서드는 델리게이트가 선언 된 클래스에서 생성 된 익명 메서드입니다. 인스턴스는 정적 메서드이므로 null입니다.

컴파일러가 대리자의 두 번째 버전에 대해 생성하는 코드를 살펴보십시오.

private static Func<string> del = new Func<string>(UserQuery.<.cctor>b__0);
private static string cctor>b__0()
{
    return UserQuery.I.ToString();
}

보시다시피, 이것은 무언가를 하는 일반적인 방법입니다 . 우리의 경우에는 ToString의 현재 인스턴스를 호출 한 결과를 반환합니다 I.


1
@flindeberg : int 대신 자체 클래스를 사용할 수도 있습니다. 기본 이유가 변경되지 않기 때문에 여전히 동일하게 작동합니다. 대리자는 특정 개체에 대한 ToString의 특정 구현을 가리 킵니다. 이것이 참조 유형인지 값 유형인지는 중요하지 않습니다.
Daniel Hilgarth 2011

3
@ user1859587 : 델리게이트에는 메서드와 대상 (인스턴스)이 있습니다. 인스턴스를 캡처하거나 람다 함수의 인스턴스에 인스턴스에 대한 참조가 포함되어 있는지 여부가 중요합니다.
flindeberg

1
@ user1859587 : 천만에요. BTW : 여기서 무슨 일이 일어나고 있는지 좀 더 명확하게하기 위해 답변을 업데이트하려고했습니다. 다시 읽어보고 싶을 수도 있습니다 :-)
Daniel Hilgarth

3
Daniel, flindeberg의 의견을 확인하기 위해 : 귀하의 대답은 정확하지만 권투에 대한 귀하의 의견은 그렇지 않습니다. user1859587이 정확합니다. 관찰 된 동작은 대리인이 호출 수신자를 캡처한다는 사실의 결과입니다. int에 대한 ToString 호출의 수신자는 int 변수에 대한 참조가되지만 대리자는 힙에 int 변수에 대한 참조를 넣을 방법이 없습니다. 변수에 대한 참조 는 임시 저장소에만 들어갈 수 있습니다. 그래서 그것은 차선책을 수행합니다. int를 상자에 넣고 해당 힙 위치에 대한 참조를 만듭니다.
에릭 리퍼 트

10
수신자가 boxed라는 사실의 흥미로운 결과는 nullable int에 대해 GetValueOrDefault ()에 대한 대리자를 만들 수 없다는 것입니다. GetValueOrDefault () 메서드가 없습니다.
Eric Lippert

4

함수가 생성 될 때가 아니라 적절한 시간에 실행될 수 I있도록 함수 에 전달해야합니다 I.ToString().

class Program
{
    public static int I = 0;

    static Func<int, string> del = num => num.ToString();

    static void Main(string[] args)
    {
        I = 10;
        Console.WriteLine("{0}", del(I));
    }
}

1

이 작업을 수행하는 방법은 다음과 같습니다.

using System;

namespace ConsoleApplication1
{

    class Program
    {
        public static int I = 0;

        static Func<string> del = new Func<string>(() => {
            return I.ToString();
        });

        static void Main(string[] args)
        {
            I = 10;
            Console.WriteLine("{0}", del());
        }
    }
}

0

C # 대리자는 개체와 인스턴스 및 메서드를 모두 캡슐화 할 수 있습니다. 대리자 선언은 System.Delegate 클래스에서 파생 된 클래스를 정의합니다. 델리게이트 인스턴스는 호출 목록을 캡슐화합니다.이 목록은 하나 이상의 메서드 목록이며 각 메서드는 호출 가능한 엔터티라고합니다.

더 많은 양식을 배우십시오

http://asp-net-by-parijat.blogspot.in/2015/08/what-is-delegates-in-c-how-to-declare.html


-2

내 생각에 int는 참조가 아닌 값에 의해 전달되기 때문에 대리자를 만들 때 현재 값 "I"(0)의 ToString 메서드에 대한 대리자입니다.


2
추측이 정확하지 않습니다. 이것은 값 유형 대 참조 유형과는 관련이 없습니다. 참조 유형에서도 똑같은 일이 발생합니다.
Daniel Hilgarth

실제로 예를 들어 클래스 인스턴스를 사용하고 ToString이 반환 값에 대한 인스턴스 데이터를 조작하는 경우 대리자가 생성되었을 때 클래스의 상태가 아니라 현재 클래스 상태의 반환 값을 생성합니다. 대리자가 생성되고 클래스의 인스턴스가 하나만있는 경우 함수가 실행되지 않습니다.
Yshayy

Func <string> (() => I.ToString ()) 메서드 호출까지 "I"를 사용하지 않기 때문에 잘 작동합니다.
Yshayy

그러나 그것은 여기서 일어나는 일과 동일하지 않습니다. Foo대신 클래스 를 사용하고 intI = 10을 변경 I = new Foo(10)하면 현재 코드와 똑같은 결과를 얻을 수 있습니다. I.Value = 10완전히 다른 것입니다. 이것은에 새 인스턴스를 할당하지 않습니다 I. 그러나 I여기 에서 새 인스턴스를 할당하는 것이 중요합니다.
Daniel Hilgarth

2
좋습니다. 문제는 "I"를 재 할당하는 것입니다. "I"가 변경 가능하고 I를 재할 당하지 않고 객체를 변경하면 작동 할 것입니다. 이 예에서는 I가 int (불변)이기 때문에 할 수 없습니다.
Yshayy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.