【问题标题】:Assigning code to a variable将代码分配给变量
【发布时间】:2014-05-31 22:20:12
【问题描述】:

是否可以做一个变量,给它赋值一行代码,比如:

ButtonClicked = (MessageBox.Show("Hello, World!"));

...所以当我使用变量时,它会执行这行代码。

【问题讨论】:

  • +1 用于编码新手和提出好问题的罕见组合:您了解自己想要做什么并很好地解释它,您只是不知道它的术语,所以您可以不是你自己找到的。
  • 您要查找的术语是delegate。
  • stackoverflow.com/questions/6187944/… 看看这个,我认为你需要足够的解释。在这方面,Asp 的工作方式几乎与 winforms 类似。
  • 听起来很像 Objective-c 中的块

标签: c# variables


【解决方案1】:

在您的情况下,您想使用delegate。

让我们看看委托是如何工作的,以及我们如何通过理解其概念来获得更简单的表单:

// Create a normal function
void OnButtonClick()
{
    MessageBox.Show("Hello World!");
}
// Now we create a delegate called ButtonClick
delegate void ButtonClick();

你看,委托采用普通函数的形式,但没有任何参数(它可以像任何其他方法一样接受任意数量的参数,但为了简单起见,它没有)。

现在,让我们使用我们所拥有的;我们将像定义任何其他变量一样定义委托:

ButtonClick ButtonClicked = new ButtonClick(OnButtonClick);

我们基本上创建了一个名为 ButtonClicked 的新变量,它具有 ButtonClick 类型(它是一个委托),并且在使用时将执行 OnButtonClick() 方法中的方法。
要使用它,我们只需调用:ButtonClicked();

所以整个代码是:

delegate void ButtonClick();

void OnButtonClick()
{
    MessageBox.Show("Hello World!");
}

void Foo()
{
    ButtonClick ButtonClicked = new ButtonClick(OnButtonClick);
    ButtonClicked(); // Execute the function.
}  

从这里开始,我们可以转到 lambda 表达式,看看它们如何在您的情况下发挥作用:
.NET 库已经定义了许多委托,其中一些像 Action,它们不接受任何参数并且不返回值。定义为public delegate void Action();
您始终可以根据需要使用它,而不是每次都需要定义一个新的委托。例如,在前面的上下文中,您可能只是写了

Action ButtonClicked = new Action(OnButtonClick);
ButtonClicked();

它会做同样的事情。
现在您已经了解了如何使用委托的不同方式,让我们使用我们的第一个 lambda 表达式。 Lambda 表达式是匿名函数;因此,它们是正常功能,但没有名称。它们具有以下形式:

x => DoSomethingWithX(x);
(x) => DoSomethingWithX(x);
(x,y) => DoSometingWithXY(x,y);
() => Console.WriteLine("I do not have parameters!");

在我们的例子中,我们没有任何参数,所以我们将使用最后一个表达式。我们可以将其用作 OnButtonClick 函数,但我们获得了没有命名函数的优势。我们可以这样做:

Action ButtonClicked = new Action( () => MessageBox.Show("Hello World!") );

甚至更简单,

Action ButtonClicked = () => MessageBox.Show("Hello World!");

然后直接调用ButtonClicked();当然你也可以有多行代码,但我不想让你更困惑。它看起来像这样:

Action ButtonClicked = () => 
{
    MessageBox.Show("Hello World!");
};
ButtonClicked();

你也可以玩转,例如,你可以执行一个这样的函数:

new Action(() => MessageBox.Show("Hello World!"))();

抱歉,帖子太长了,希望不要太混乱:)

编辑:我忘了提到另一种形式,即使不经常使用,也可以使 lambda 表达式更容易理解:

new Action(delegate() {
    Console.WriteLine("I am parameterless");
})();

另外,使用泛型:

// Defines a delegate that has one parameter of type string. You could pass as many parameters as you want.
new Action<string>(delegate(string x) {
    Console.WriteLine(x);
})("I am a string parameter!");

反过来,您可以使用 lambda 表达式,但您不需要(但在某些情况下可能)定义参数的类型,例如,上面的代码可以简单地写成:

new Action<string>(x => {
    Console.WriteLine(x);
})("I am a string parameter!");

或:

new Action<string>(x => Console.WriteLine(x))("I am a string parameter!");

EDIT2:
Action&lt;string&gt; 是public void delegate Action(string obj); 的表示
Action&lt;string,string&gt; 是public void delegate Action(string obj, string obj2); 的表示
一般来说,Action&lt;T&gt;是public void delegate Action&lt;T&gt;(T obj);的表示

编辑3: 我知道这个帖子已经在这里有一段时间了,但我认为这真的很酷,不用提: 您可以这样做,这主要与您的问题有关:

dynamic aFunction = (Func<string, DialogResult>)MessageBox.Show;
aFunction("Hello, world!");

或者简单地说:

Func<string, DialogResult> aFunction = MessageBox.Show;
aFunction("Hello, world!");

【讨论】:

    【解决方案2】:

    您可以将 C# 代码分配给变量,在运行时编译并运行代码:

    • 编写你的代码:

      // Assign C# code to the code variable.
      string code = @"
      using System;
      
      namespace First
      {
          public class Program
          {
              public static void Main()
              {
                  " +
                  "Console.WriteLine(\"Hello, world!\");"
                  + @"
              }
          }
      }
      ";
      
    • 创建编译器的提供者和参数:

      CSharpCodeProvider provider = new CSharpCodeProvider();
      CompilerParameters parameters = new CompilerParameters();
      
    • 定义编译器的参数:

      // Reference to System.Drawing library
      parameters.ReferencedAssemblies.Add("System.Drawing.dll");
      // True - memory generation, false - external file generation
      parameters.GenerateInMemory = true;
      // True - exe file generation, false - dll file generation
      parameters.GenerateExecutable = true;
      
    • 编译程序集:

      CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
      
    • 检查错误:

      if (results.Errors.HasErrors)
      {
              StringBuilder sb = new StringBuilder();
      
              foreach (CompilerError error in results.Errors)
              {
                      sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
              }
      
              throw new InvalidOperationException(sb.ToString());
      }
      
    • 获取程序集、类型和 Main 方法:

      Assembly assembly = results.CompiledAssembly;
      Type program = assembly.GetType("First.Program");
      MethodInfo main = program.GetMethod("Main");
      
    • 运行它:

      main.Invoke(null, null);
      

    参考:

    http://www.codeproject.com/Tips/715891/Compiling-Csharp-Code-at-Runtime

    【讨论】:

    • 我认为动态代码编译与所提出的问题完全无关。
    【解决方案3】:

    Lazy 类专门用于表示一个在您请求之前不会计算的值。您可以通过提供一个定义它应该如何构造的方法来构造它,但是它将处理执行该方法的次数不超过一次(即使面对多个请求该值的线程),并且只需为任何其他请求返回已经构造的值:

    var foo = new Lazy<DialogResult>(()=>MessageBox.Show("Hello, World!"));
    
    var result = foo.Value;
    

    【讨论】:

    • 请记住 Lazy 应该用于需要大量处理能力的值,并且您不应该将它们用于交互(因为 .Value 的语义是它返回一个值,类似于属性,而不是(交互式)操作)。应该将委托用于此类操作。
    • @Abel 不,它 不是 用于需要大量处理能力的值,它适用于您希望推迟初始化直到被请求的任何值,而不会多次初始化该值。这里使用了Value 的值;它是从显示消息框收到的DialogResult。此解决方案与使用委托之间的主要区别在于是否应在每次请求时重新计算该值。我对要求的解释是,这是在概念上初始化一个值,而不是要重复的 操作。
    • Lazy 很容易被错误使用。它有自己的开销,使用它“只是”推迟一个小任务将调用比它获得更多的开销。无论Lazy 如何,显示来自属性的消息框通常是(imo)不好的做法。顺便说一句,来自 MSDN,我引用:“使用延迟初始化来推迟创建大型或资源密集型对象”。你可以不同意这一点,但这就是它最初的设计目的。
    • @Abel Lazy 在这样的上下文中的性能开销当然可以忽略不计;与等待人们单击消息框所花费的时间相比,它会显得苍白无力。它主要归结为底层应用程序的实际需求;问题的模糊性使得客观正确的答案变得不可能。这是对问题的一种解释。至于在属性吸气剂中做很多工作不好;显然你从根本上反对Lazy 的整个设计。欢迎您提出这种意见。
    • 对不起,你一定误会了我。当然,MessageBox 的开销可以忽略不计(我只是不会在属性内使用 UI)。我指的是一般的小任务(比如推迟2 + 3 * 4 / i),其中创建闭包的开销大于计算本身。而且我认为我完全接受Lazy,事实上我们在 F# 中经常使用它(在 C# 中很少使用它)并且我们已经学会了你必须小心它的艰难方法,尤其是。在性能方面。
    【解决方案4】:

    您可以像这样将其分配给Action:

    var ButtonClicked = new Action(() => MessageBox.Show("hi"));
    

    然后调用它:

    ButtonClicked();
    

    为了完整性(关于各种 cmets)...

    正如 Erik 所说,您可以执行多行代码:

    var ButtonClicked = new Action(() =>
    {
        MessageBox.Show("hi");
    
        MessageBox.Show("something else");  // something more useful than another popup ;)
    });
    

    正如 Tim 所说,您可以省略 Action 关键字

    Action ButtonClicked = () => MessageBox.Show("hi");
    
    Action ButtonClicked = () =>
    {
        // multiple lines of code
    };
    

    为了解决 KRyan 的评论,关于空括号,它表示您希望能够发送到操作的参数列表(在这种情况下,没有)。

    例如,如果您想指定要显示的消息,则可以将“消息”添加为参数(请注意,我已将 Action 改为 @ 987654322@为了指定单个字符串参数):

    Action<string> ButtonClicked = (message) => MessageBox.Show(message);
    
    ButtonClicked("hello world!");
    

    【讨论】:

    • Action ButtonClicked = () =&gt; MessageBox.Show("hi"); 是等效的,并且 IMO 更好(如果您愿意,可以添加括号)
    • 动作也可以解析为多行代码。
    • @CSharpie 我不确定做出这样的假设是否对 OP 有帮助。
    • @CSharpie 为什么 OP 不能在 WinForms 中使用它?
    • @CSharpie 我明白你在说什么。如果他实际上将此附加到 Button.Click 事件,而不是将其存储在他碰巧命名为 ButtonClicked 的变量中。
    【解决方案5】:

    我正在阅读您的问题的方式,这是在 GUI 控件的上下文中?

    如果这是在 WPF 中,请查看处理来自控件的命令的“正确”方式: http://msdn.microsoft.com/en-us/library/ms752308(v=vs.110).aspx

    ...但这可能是一种痛苦和矫枉过正。对于更简单的一般情况,您可能正在寻找事件处理程序,例如:

    myButton.Click += (o, e) => MessageBox.Show("Hello, World!");
    

    可以通过多种方式处理该事件处理程序。上面的例子使用了一个匿名函数,但你也可以这样做:

    Action<object, RoutedEventArgs> sayHello = (o, e) => MessageBox.Show("Hello, World");
    myButton.Click += new RoutedEventHandler(sayHello);
    

    ...就像您问的那样,将函数(或此处为“Action”,因为它返回 void)分配为变量。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-21
      • 2014-05-11
      相关资源
      最近更新 更多