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

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));

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



답변

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

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);
}


답변

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

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());
    }
}


답변

사람들이 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);


답변

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

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”입니다. 예, 많은 호출 오버 헤드이지만 짧은 목록과 훨씬 더 간단한 코드에서는 무시할 수 있습니다.


답변

@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는 그만한 가치가 없습니다.


답변

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

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


답변

 this.Dispatcher.Invoke((Action)(() => { textBox1.Text = "Test 123"; }));