일반 대리자 매개 변수로 제공 될 때 람다 식을 캐스팅해야하는 이유


124

System.Windows.Forms.Control.Invoke (Delegate method) 메서드를 사용합니다.

왜 컴파일 시간 오류가 발생합니까?

string str = "woop";
Invoke(() => this.Text = str);
// Error: Cannot convert lambda expression to type 'System.Delegate'
// because it is not a delegate type

그러나 이것은 잘 작동합니다.

string str = "woop";
Invoke((Action)(() => this.Text = str));

메소드가 일반 대리자를 예상하는 경우?

답변:


125

람다 식은 대리자 형식 또는 식 트리로 변환 할 수 있지만 어떤 대리자 형식 을 알아야 합니다. 서명을 아는 것만으로는 충분하지 않습니다. 예를 들어 다음이 있다고 가정합니다.

public delegate void Action1();
public delegate void Action2();

...

Delegate x = () => Console.WriteLine("hi");

에서 참조하는 객체의 구체적인 유형은 x무엇일까요? 예, 컴파일러 는 적절한 서명을 사용하여 새 대리자 형식을 생성 할 수 있지만 이는 거의 유용하지 않으며 결국 오류 검사 기회가 줄어 듭니다.

당신이 전화에 쉽게 그것을 확인하려면 Control.Invoke과 함께 Action할 수있는 가장 쉬운 방법은 컨트롤에 확장 메서드를 추가 할 수 있습니다 :

public static void Invoke(this Control control, Action action)
{
    control.Invoke((Delegate) action);
}

1
감사합니다. 입력하지 않은 용어가 잘못된 용어라고 생각하여 질문을 업데이트했습니다.
xyz

1
그것은 매우 우아하고 성숙한 해결책입니다. 나는 아마도 그것을 "InvokeAction"이라고 부를 것입니다. 그래서 그 이름은 우리가 (일반 델리게이트 대신) 실제로 호출하는 것을 제안하지만 확실히 저에게 효과적입니다 :)
Matthias Hryniszak

7
나는 그것이 "드물게 유용하고 ..."라는 것에 동의하지 않는다. 람다를 사용하여 Begin / Invoke를 호출하는 경우 대리자 형식이 자동 생성되었는지 여부는 확실히 신경 쓰지 않고 호출을 받고 싶습니다. Delegate (기본 유형)를 받아들이는 메서드는 어떤 상황에서 구체적인 유형이 무엇인지에 관심이 있습니까? 또한 확장 방법의 목적은 무엇입니까? 더 쉽게 만들 수는 없습니다.
Tergiver 2011 년

5
아! 확장 방법을 추가하고 시도했지만 Invoke(()=>DoStuff)여전히 오류가 발생했습니다. 문제는 내가 암시적인 'this'를 사용했다는 것입니다. Control 멤버 내에서 작동하도록하려면 명시 적이어야 this.Invoke(()=>DoStuff)합니다..
Tergiver 2011 년

2
이 글을 읽는 다른 사람들에게는 C #에 대한 질문 및 답변 : InvokeRequired 코드 패턴 자동화 가 매우 유용 하다고 생각합니다 .
Erik Philips

34

람다를 반복해서 캐스팅하는 데 지치 셨나요?

public sealed class Lambda<T>
{
    public static Func<T, T> Cast = x => x;
}

public class Example
{
    public void Run()
    {
        // Declare
        var c = Lambda<Func<int, string>>.Cast;
        // Use
        var f1 = c(x => x.ToString());
        var f2 = c(x => "Hello!");
        var f3 = c(x => (x + x).ToString());
    }
}

3
그것은 제네릭의 아름다운 사용입니다.
Peter Wone 2011

2
나는 인정해야 할 것입니다. 이것이 왜 효과가 있는지 알아내는 데 시간이 걸렸습니다. 훌륭한. 안타깝지만 지금은 쓸모가 없습니다.
William

1
이것의 사용법을 설명해 주시겠습니까? 내가 이것을 이해하기 어렵습니까? 감사합니다.
shahkalpesh

이것을 읽는 것은 말할 것도없고, 나는 Jon Skeet의 대답보다이 대답을 선호한다고 생각합니다!
Pogrindis 2013 년

@shahkalpesh는 그다지 복잡하지 않습니다. 이런 식으로 보면 Lambda<T>클래스에는 Cast전달 된 모든 항목을 반환 하는라는 ID 변환 메서드 가 있습니다 ( Func<T, T>). 이제이 Lambda<T>로 선언 Lambda<Func<int, string>>당신이를 통과하면 어떤 수단 Func<int, string>Cast방법, 그것은 반환 Func<int, string>이후, 다시 T이 경우는에 Func<int, string>.
nawfal

12

사람들이 UI 스레드에 마샬링을 시도하기 때문에 9/10의 시간이이 정보를 얻습니다. 게으른 방법은 다음과 같습니다.

static void UI(Action action) 
{ 
  System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(action); 
}

이제 입력 했으므로 문제는 사라지고 (qv Skeet의 답변) 매우 간결한 구문이 있습니다.

int foo = 5;
public void SomeMethod()
{
  var bar = "a string";
  UI(() =>
  {
    //lifting is marvellous, anything in scope where the lambda
    //expression is defined is available to the asynch code
    someTextBlock.Text = string.Format("{0} = {1}", foo, bar);        
  });
}

보너스 포인트에 대한 또 다른 팁이 있습니다. UI에 대해서는이 작업을 수행하지 않지만 완료 될 때까지 차단할 SomeMethod가 필요한 경우 (예 : 요청 / 응답 I / O, 응답 대기) WaitHandle (qv msdn WaitAll, WaitAny, WaitOne)을 사용합니다.

AutoResetEvent는 WaitHandle 파생물입니다.

public void BlockingMethod()
{
  AutoResetEvent are = new AutoResetEvent(false);
  ThreadPool.QueueUserWorkItem ((state) =>
  {
    //do asynch stuff        
    are.Set();
  });      
  are.WaitOne(); //don't exit till asynch stuff finishes
}

얽히게 될 수있는 마지막 팁 : WaitHandles가 스레드를 지연시킵니다. 이것이 그들이해야 할 일입니다. 지연된 상태 에서 UI 스레드에 마샬링하려고하면 앱이 중단됩니다. 이 경우 (a) 심각한 리팩토링이 필요하며 (b) 임시 해킹으로 다음과 같이 기다릴 수 있습니다.

  bool wait = true;
  ThreadPool.QueueUserWorkItem ((state) =>
  {
    //do asynch stuff        
    wait = false;
  });
  while (wait) Thread.Sleep(100);

3
나는 사람들이 단지 개인적으로 그것이 매력적이라고 ​​생각하지 않는다는 이유만으로 대답에 투표 할 수있는 뺨을 가지고 있다는 것이 매력적이라고 ​​생각합니다. 그것이 잘못 되었고 이것을 알고 있다면 무엇이 잘못되었는지 말하십시오. 그렇게 할 수 없다면 반대 투표를 할 근거가 없습니다. 심하게 잘못된 경우 "Baloney. [정답] 참조"또는 "권장 솔루션이 아님, [더 나은 제품] 참조"와 같이 말하십시오.
Peter Wone

1
예, 저는 frankenthreadstress입니다. 그러나 어쨌든 나는 그것이 왜 투표가 거절되었는지 전혀 모른다. 비록 실제 코드를 사용하지는 않았지만, 이것이 UI 크로스 스레드 호출에 대한 멋진 빠른 소개라고 생각했고, 제가 그렇게 명성을 얻지 못했던 것들이 있습니다. :) 내 말은, 당신은 델리게이트 호출을위한 멋진 빠른 방법을 제공했습니다; 기다려야하는 통화 옵션을 제공합니다. UI Thread Hell에 갇혀있는 사람이 약간의 제어권을 되 찾을 수있는 멋진 빠른 방법을 따라 가면됩니다. 정답입니다. + <3도 말 할게요. :)
shelleybutterfly

System.Windows.Threading.Dispatcher.CurrentDispatcherCURRENT 스레드의 디스패처를 반환합니다. 즉, UI 스레드가 아닌 스레드에서이 메서드를 호출하면 코드가 UI 스레드에서 실행되지 않습니다.
BrainSlugs83

@ BrainSlugs83 좋은 점은 아마도 앱이 UI 스레드 디스패처에 대한 참조를 캡처하여 전역 적으로 액세스 할 수있는 곳에 배치하는 것입니다. 누군가가 그것을 알아 차리는 데 이렇게 오래 걸렸다는 것이 놀랍습니다!
Peter Wone

4

피터 원. 당신은 남자입니다. 당신의 개념을 조금 더 나아가서이 두 가지 기능을 생각해 냈습니다.

private void UIA(Action action) {this.Invoke(action);}
private T UIF<T>(Func<T> func) {return (T)this.Invoke(func);}

이 두 함수를 Form 앱에 배치하고 다음과 같이 백그라운드 작업자에서 호출 할 수 있습니다.

int row = 5;
string ip = UIF<string>(() => this.GetIp(row));
bool r = GoPingIt(ip);
UIA(() => this.SetPing(i, r));

아마도 약간 게으르지 만 작업자 완료 기능을 설정할 필요가 없습니다. 이와 같은 경우 매우 편리합니다.

private void Ping_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
  int count = this.dg.Rows.Count;
  System.Threading.Tasks.Parallel.For(0, count, i => 
  {
    string ip = UIF<string>(() => this.GetIp(i));
    bool r = GoPingIt(ip);
    UIA(() => this.SetPing(i, r));
  });
  UIA(() => SetAllControlsEnabled(true));
}

기본적으로 GUI DataGridView에서 일부 ip 주소를 가져 와서 ping하고 결과 아이콘을 녹색 또는 빨간색으로 설정하고 양식에서 버튼을 다시 활성화합니다. 예, 백그라운드 작업자의 "parallel.for"입니다. 예, 많은 호출 오버 헤드이지만 짧은 목록과 훨씬 더 간단한 코드에서는 무시할 수 있습니다.


1

@Andrey Naumov 의 답변 을 기반으로 이것을 구축하려고했습니다 . 이것은 약간의 개선 일 수 있습니다.

public sealed class Lambda<S>
{
    public static Func<S, T> CreateFunc<T>(Func<S, T> func)
    {
        return func;
    }

    public static Expression<Func<S, T>> CreateExpression<T>(Expression<Func<S, T>> expression)
    {
        return expression;
    }

    public Func<S, T> Func<T>(Func<S, T> func)
    {
        return func;
    }

    public Expression<Func<S, T>> Expression<T>(Expression<Func<S, T>> expression)
    {
        return expression;
    }
}

여기서 type 매개 변수 S는 형식 매개 변수 (나머지 유형을 추론하는 데 필요한 최소 입력 매개 변수)입니다. 이제 다음과 같이 호출 할 수 있습니다.

var l = new Lambda<int>();
var d1 = l.Func(x => x.ToString());
var e1 = l.Expression(x => "Hello!");
var d2 = l.Func(x => x + x);

//or if you have only one lambda, consider a static overload
var e2 = Lambda<int>.CreateExpression(x => "Hello!");

당신은 추가 오버로드 할 수 있습니다 Action<S>Expression<Action<S>>유사하게 같은 클래스를. 들어 다른 대의원과 표현 유형에 내장, 당신은 같은 별도의 클래스를 작성해야합니다 Lambda, Lambda<S, T>, Lambda<S, T, U>

이것의 장점은 원래 접근 방식에 비해 다음과 같습니다.

  1. 유형 사양이 하나 적습니다 (정식 매개 변수 만 지정하면 됨).

  2. 예에서 볼 수 있듯이 말이 Func<int, T>있을 때뿐만 아니라 모든 에 대해 자유롭게 사용할 수 있습니다.Tstring

  3. 즉시 표현을 지원합니다. 이전 접근 방식에서는 다음과 같이 유형을 다시 지정해야합니다.

    var e = Lambda<Expression<Func<int, string>>>.Cast(x => "Hello!");
    
    //or in case 'Cast' is an instance member on non-generic 'Lambda' class:
    var e = lambda.Cast<Expression<Func<int, string>>>(x => "Hello!");

    표현을 위해.

  4. 다른 대리자 (및 식) 유형에 대해 클래스를 확장하는 것도 위와 비슷하게 번거 롭습니다.

    var e = Lambda<Action<int>>.Cast(x => x.ToString());
    
    //or for Expression<Action<T>> if 'Cast' is an instance member on non-generic 'Lambda' class:
    var e = lambda.Cast<Expression<Action<int>>>(x => x.ToString());

내 접근 방식에서는 유형을 한 번만 선언해야합니다 ( Funcs의 경우 너무 적음 ).


Andrey의 대답을 구현하는 또 다른 방법은 완전히 일반화되지 않는 것과 같습니다.

public sealed class Lambda<T>
{
    public static Func<Func<T, object>, Func<T, object>> Func = x => x;
    public static Func<Expression<Func<T, object>>, Expression<Func<T, object>>> Expression = x => x;
}

따라서 상황은 다음과 같이 줄어 듭니다.

var l = Lambda<int>.Expression;
var e1 = l(x => x.ToString());
var e2 = l(x => "Hello!");
var e3 = l(x => x + x);

그것은 더 적은 타이핑이지만 특정 유형 안전성을 잃고 imo는 그만한 가치가 없습니다.


1

파티에 조금 늦었지만 이렇게 캐스팅 할 수도 있어요

this.BeginInvoke((Action)delegate {
    // do awesome stuff
});


0

XUnit 및 Fluent Assertion 사용 를 사용하여이 인라인 기능을 정말 멋지게 사용할 수있었습니다.

전에

[Fact]
public void Pass_Open_Connection_Without_Provider()
{
    Action action = () => {
        using (var c = DbProviderFactories.GetFactory("MySql.Data.MySqlClient").CreateConnection())
        {
            c.ConnectionString = "<xxx>";
            c.Open();
        }
    };

    action.Should().Throw<Exception>().WithMessage("xxx");
}

[Fact]
public void Pass_Open_Connection_Without_Provider()
{
    ((Action)(() => {
        using (var c = DbProviderFactories.GetFactory("<provider>").CreateConnection())
        {
            c.ConnectionString = "<connection>";
            c.Open();
        }
    })).Should().Throw<Exception>().WithMessage("Unable to find the requested .Net Framework Data Provider.  It may not be installed.");
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.