-1

i'm trying to switch from VB. Net to C# (both .Net framework and .Net &) so i started to "translate" some function that i use quite common. But i'm facing some problems (i have zero clue in C based languages).

So, this is the working VB .Net function i used for years, is a simple cross-thread solution when i call control from a different thread, task, etc.:

Imports System.Linq.Expressions
Imports System.Runtime.CompilerServices

Module Module1

    Private Delegate Sub InvokeThreadSafeMethodDelegate(ByVal Cnt As Control, ByVal Mtd As Expression(Of Action))
    Private Delegate Function InvokeThreadSafeFunctionDelegate(Of T)(ByVal Cnt As Control, ByVal Fnc As Expression(Of Func(Of T))) As T


    <Extension()>
    Public Sub InvokeThreadSafeMethod(ByVal Cnt As Control, ByVal Mtd As Expression(Of Action))
        If (Cnt.InvokeRequired) Then
            Dim Dlg = New InvokeThreadSafeMethodDelegate(AddressOf InvokeThreadSafeMethod)
            Cnt.Invoke(Dlg, Cnt, Mtd)
        Else
            Mtd.Compile().DynamicInvoke()
        End If
    End Sub


    <Extension()>
    Public Function InvokeThreadSafeFunction(Of T)(ByVal Cnt As Control, ByVal Fnc As Expression(Of Func(Of T))) As T
        If (Cnt.InvokeRequired) Then
            Dim Dlg = New InvokeThreadSafeFunctionDelegate(Of T)(AddressOf InvokeThreadSafeFunction)
            Return DirectCast(Cnt.Invoke(Dlg, Cnt, Fnc), T)
        Else
            Return DirectCast(Fnc.Compile().DynamicInvoke(), T)
        End If
    End Function

End Module

And i call them both with that:

Me.Label1.InvokeThreadSafeMethod(Sub() Me.Label1.text = "Test")

Dim X as String = Me.Label1.InvokeThreadSafeFunction(Function() Me.Label1.text)

After translateing it to C# i got this:

using System.Linq.Expressions;

internal static class Cls_Comune
{

    private delegate void InvokeThreadSafeMethodDelegate(Control C, Expression<Action> M);
    private delegate T InvokeThreadSafeFunctionDelegate<T>(Control C, Expression<Func<T>> F);

    public static void InvocaMetodoSicuro(this Control C, Expression<Action> M)
    {
        if (C.InvokeRequired)
        {
            var D = new InvokeThreadSafeMethodDelegate(InvocaMetodoSicuro);
            C.Invoke(D, C, M);
        }
        else
            M.Compile().DynamicInvoke();
    }

    public static T InvocaFunzioneSicuro<T>(this Control C, Expression<Func<T>> F)
    {
        if (C.InvokeRequired)
        {
            var D = new InvokeThreadSafeFunctionDelegate<T>(InvocaFunzioneSicuro);
            return (T)C.Invoke(D, C, F);
        }
        else
            return (T)F.Compile().DynamicInvoke();
    }
}

Here, in this line return (T)F.Compile().DynamicInvoke(); i got an error about the conversion of Null variables. In VB .Net i have used a Directcast that seems to not be present in C#. But the main problem is, how i can call them? Because this, give me 2 different errors:

private async void Naviga(string strLnk)
        {
            await Task.Run(void () =>
            {
                Txt_Stato.InvocaMetodoSicuro(() => Txt_Stato.Text = "Test");

                WW2.InvocaMetodoSicuro(() => WW2.Source = new System.Uri(strLnk, UriKind.Absolute));

                string str = WW2.InvocaFunzioneSicuro(() => WW2.Source.ToString());
            }

            );
        }

In the Task, the first line (Txt_Stato) give me "InvocaMetodoSicuro is not a part of ToolStripLabel". As it can't take the extension from the stati class. The second line (WW2 is a WebView2) give me "An expression tree may not contain an assignment operator." The third line (string str...) give me no errors, but i dont know if works because the debug wont start due to the "Null error" in the static class.

Again, i'm trying with self-learning to switch from VB .Net to C# and i want to convert some useful functions that i had in VB .Net. So, if the C# is a mess, think that i just started to code with it 2 hours ago. So, what is the problem in the code?

Tyler
  • 416
  • 4
  • 11
  • 1
    The following may be helpful: https://stackoverflow.com/questions/8983137/how-to-update-a-richtextbox-from-backgroundworker-using-begininvoke – Tu deschizi eu inchid Feb 13 '22 at 02:00
  • @user9938 yes, thanks. This help me to fix the first function, the one where i send a control and a void, but not the second one, where instead of the void, must be a function. For let you understand better, in the Tanks, the first 2 code (`Txt_Stato` and `WW2`) works now, but not the `string str = ...` because isn't a void. – Tyler Feb 13 '22 at 13:47

1 Answers1

1

I don't know why you're using Expression<Action> and Expression<Func<T>> when Action and Func<T> would suffice. You're not using Expression<> in any meaningful way.

Your code can simply be rewritten as this:

public static void InvocaSicuro(this Control C, Action M)
{
    if (C.InvokeRequired)
        C.Invoke(M);
    else
        M();
}

public static T InvocaSicuro<T>(this Control C, Func<T> F) =>
    C.InvokeRequired
    ? C.Invoke(F)
    : F();

You can even re-use the same name (as I have done) as method overloading can figure it all out for you.

I would suggest going one step further and adding the following signatures:

public static void InvocaSicuro<C>(this C control, Action<C> action) where C : Control
{
    if (control.InvokeRequired)
        control.Invoke(() => action(control));
    else
        action(control);
}

public static T InvocaSicuro<C, T>(this C control, Func<C, T> func) where C : Control =>
    control.InvokeRequired
    ? control.Invoke(() => func(control))
    : func(control);

These versions are invoked against the calling control and make refactoring a little safer and it reduces repetition.

For example, this:

Txt_Stato.InvocaSicuro(() => Txt_Stato.Text = "Test");

...becomes:

Txt_Stato.InvocaSicuro(c => c.Text = "Test");

The above code works fine in .NET 6.0 as new Invoke overloads were added in core.

For 4.7.2 you need the following code:

public static void InvocaSicuro(this Control control, Action action)
{
    if (control.InvokeRequired)
        control.Invoke(action);
    else
        action();
}

public static T InvocaSicuro<T>(this Control control, Func<T> func) =>
    control.InvokeRequired
    ? (T)control.Invoke((Delegate)func)
    : func();

public static void InvocaSicuro<C>(this C control, Action<C> action) where C : Control
{
    if (control.InvokeRequired)
        control.Invoke((Action)(() => action(control)));
    else
        action(control);
}

public static T InvocaSicuro<C, T>(this C control, Func<C, T> func) where C : Control =>
    control.InvokeRequired
    ? (T)control.Invoke((Action)(() => func(control)))
    : func(control);
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • Yeah, that works, thanks! I have some questions, isn't DynamicInvoke better than Invoke? And, how i can use these function for a child control? I mean, let's suppose i want to change the text of the ToolStripLabel1 inside a ToolStrip1, i can't now because if i use them like `ToolStripLabel1.InvocaSicuro(c => c.Text = "Test");` will not work because i should call the `ToolStrip1`, but `ToolStrip1.InvocaSicuro(c => c.Text = "Test");` will not change the Label1 or LabelN text. – Tyler Feb 15 '22 at 18:29
  • @Tyler - Here's a discussion on `Invoke` versus `DynamicInvoke`. – Enigmativity Feb 15 '22 at 19:37
  • @Tyler - I suggested you add those two extra signatures - I didn't say you should remove the first two. The first two still have their uses. – Enigmativity Feb 15 '22 at 19:38
  • I understand. Anyway, i have added the 2 codes in a library project and both give me error now (while in a normal form project they work). The error is [this](https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs1660?f1url=%3FappId%3Droslyn%26k%3Dk(CS1660)). I have solved the `action` one with `control.Invoke((Action)(() => action(control)));` but about the `func` one, i don't know how to solve, also looking online. (the transition from VB .Net to C# is quite painful) – Tyler Feb 15 '22 at 22:56
  • I tested all of the code I posted and it worked fine. Can you give me a [mcve] that demonstrates the issue? – Enigmativity Feb 16 '22 at 01:12
  • Yes, in a WinForm app it works fine. But i have created a NEW library class project (DLL) and just pasted the code inside [screenshot](https://i.postimg.cc/wjS75HFz/00100-2022-02-16-002.png) but it give me [this](https://learn.microsoft.com/en-us/dotnet/csharp/misc/cs1660?f1url=%3FappId%3Droslyn%26k%3Dk(CS1660)) error. I have solved (maybe) the first one with the line in the comment on the left, but the other one seems to not work in any way. – Tyler Feb 16 '22 at 18:22
  • It should work just fine so long as you're referencing Windows Forms in the DLL. – Enigmativity Feb 16 '22 at 22:26
  • Yes is referenced, and as you can see in the screenshot, also "using System.Windws.Forms". The problem is the conversion of the delegate – Tyler Feb 17 '22 at 23:06
  • @Tyler - But I don't get the issue with my code. For example, `var form = new Form(); form.Text = "Hello"; string text = form.InvocaSicuro(x => x.Text); Console.WriteLine(text);`, that works without any issue. I need to know what you're doing differently. – Enigmativity Feb 17 '22 at 23:56
  • ok, i will be super clear just to avoid misunderstandings. I am switching from VB .Net to C#, so i have started converting my library (DLLs). One of my common function is this one, the cross-thread solution for controls. Creating a new WINFORM app (with Visual Studio) and pasting your code insiede, it works. BUT creating a CLASS LIBRARY and pasting it there, is not working. Windows.Forms is referenced. SO, for have my same situation, just start a new project (class library dll) and paste the code in a static class. (in VB Net, the same code works in WinForm AND Class Library) TY – Tyler Feb 19 '22 at 19:34
  • @Tyler - I figured it out. It's a .NET 6.0 versus 4.7.2 issue. I've added the updated code. – Enigmativity Feb 20 '22 at 02:38
  • Yes, my bad. I forgot to mention it, sorry. Anyway, now all is working great, but you meant "the code below is for .Net 6" and the above for .Net Framework, right? – Tyler Feb 21 '22 at 21:12
  • @Tyler - No, the other way around. "the code above is for .Net 6 and the code below is for .Net Framework". – Enigmativity Feb 21 '22 at 21:14