programing tip

일반 위임 매개 변수로 제공 될 때 람다 식을 캐스트해야하는 이유

itbloger 2020. 7. 14. 19:20
반응형

일반 위임 매개 변수로 제공 될 때 람다 식을 캐스트해야하는 이유


System.Windows.Forms.Control.Invoke (Delegate 메소드) 메소드를 사용하십시오.

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

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 분의 1이 이것을 얻는다. 게으른 방법은 다음과 같습니다.

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

입력되었으므로 문제가 사라지고 (qv Skeet의 anwer) 다음과 같은 간결한 구문이 있습니다.

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에 대해서는이 작업을 수행하지 않지만 완료 될 때까지 (예 : 요청 / 응답 I / O, 응답 대기) SomeMethod를 차단해야하는 경우 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
}

And a final tip because things can get tangled: WaitHandles stall the thread. This is what they're supposed to do. If you try to marshal onto the UI thread while you have it stalled, your app will hang. In this case (a) some serious refactoring is in order, and (b) as a temporary hack you can wait like this:

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

Peter Wone. you are da man. Taking your concept a bit further, I came up with these two functions.

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

I place these two functions into my Form app, and I can make calls from background workers like this

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

Maybe a bit lazy, but i don't have to setup worker done functions, which comes in super handy in cases such as this

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

Essentially, get some ip addresses from a gui DataGridView, ping them, set the resulting icons to green or red, and reenable buttons on the form. Yes, it is a "parallel.for" in a backgroundworker. Yes it is a LOT of invoking overhead, but its negligible for short lists, and much more compact code.


I tried to build this upon @Andrey Naumov's answer. May be this is a slight improvement.

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

Where type parameter S is the formal parameter (the input parameter, which is minimum required to infer rest of the types). Now you can call it like:

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

You can have additional overloads for Action<S> and Expression<Action<S>> similarly in the same class. For other built in delegate and expression types, you will have to write separate classes like Lambda, Lambda<S, T>, Lambda<S, T, U> etc.

Advantage of this I see over the original approach:

  1. One less type specification (only the formal parameter needs to be specified).

  2. Which gives you the freedom to use it against any Func<int, T>, not just when T is say, string, as shown in examples.

  3. Supports expressions straight away. In the earlier approach you will have to specify types again, like:

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

    for expressions.

  4. Extending the class for other delegate (and expression) types is similarly cumbersome like above.

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

In my approach you have to declare types only once (that too one less for Funcs).


One another way to implement Andrey's answer is like not going fully generic

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

So things reduce to:

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

That's even less typing, but you lose certain type safety, and imo, this is not worth it.


Bit late to the party but you can also cast like this

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

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

Playing with XUnit and Fluent Assertions it was possible to use this inline capability in a way I find really cool.

Before

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

After

[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.");
}

참고URL : https://stackoverflow.com/questions/411579/why-must-a-lambda-expression-be-cast-when-supplied-as-a-plain-delegate-parameter

반응형