在Unity中使用委托和扩展方法创建更好的代码

委托和扩展方法是C#工具箱中的工具,如果使用得当,可以极大地提高代码的质量,提高其可重用性和可读性。 但是什么是委托和扩展? 以下是对代表来自Google的第一批热门歌曲的解释:

C#中的委托类似于C或C ++中的函数指针。 使用委托可以使程序员将对方法的引用封装在委托对象中。 然后可以将委托对象传递给可以调用引用方法的代码,而不必在编译时知道将调用哪个方法。 — akadia.com

换句话说,委托声明描述了方法签名(方法的返回类型及其参数类型),而该委托的实例可以被分配,引用并调用与该签名匹配的方法。 您之前可能已经遇到过Action,Func和EventHandler,它们都是委托的示例。 如果您曾经使用过Array.Find或List.ForEach之类的函数,那么您还使用过委托。 这是一个将int作为参数并返回bool的委托的示例:

扩展方法在.NET C#官方文档中的解释如下:

扩展方法使您可以将方法“添加”到现有类型,而无需创建新的派生类型,重新编译或修改原始类型。 扩展方法是一种特殊的静态方法,但是它们的调用就像是扩展类型上的实例方法一样。

最常用的扩展方法是LINQ的扩展方法。 但是,没有什么可以阻止您添加自己的扩展方法。 下面是将可链接添加方法添加到通用List类的示例:

good好

太好了,所以现在您知道什么是委托和扩展了! 但是,为什么要在代码中使用它们呢? 这是使用委托和扩展的优点的快速列表:

  • 封装和代码可重用性
  • 函数的可扩展性以及内置的C#类型和类
  • 易于理解,维护和阅读的代码

让我们在一个示例中展示它。 我通常会在自己的游戏中遍历一系列项目并根据情况执行操作或更改其状态。 想象一下,您有一个喜欢拥抱的Unicorn

假设您还有一个独角兽控制器类(对您感到羞耻),它会遍历独角兽,并在它们过于接近时使它们拥抱您的玩家:

它可以正常工作,但是这种方法很快就会变得非常混乱,特别是当我们需要扩展逻辑时。 替代方法是创建一个扩展方法,该方法使用委托来迭代List:

现在,您可以将以前的独角兽控制器类重写为:

那是一些简洁的代码! 👌

根据我的经验,这些模式会被反复使用,编写这些扩展方法值得您花时间。 扩展方法不仅可以在该项目中重复使用,而且还可以在其他项目中重复使用(也许通过创建自己的Unity包),从长远来看,这将节省大量时间。 仅对一次ForEach方法的实现进行定义一次,这会使您的代码库也更易于维护。 此外,乍一看,我发现第二种方法更容易阅读和掌握。

👾不好

太好了! 但是,在Unity中使用这样的委托和扩展也存在一些潜在的陷阱:

  • 学习曲线
  • 意外创建垃圾(GC)

许多人发现很难将他们束之高阁,为什么为什么有时他们比常规函数调用更受青睐。 有一个初步的学习曲线,但是一旦通过,就很容易掌握。 如果您正在使用委托创建扩展方法库,那么新开发人员也很难立即理解代码(在这里,扩展方法的好名字才是真正的关键)。 您总是可以指向此博客文章,以摆脱学习曲线问题😊但是,无意间创建垃圾会加剧问题的严重性,并有可能造成伤害世界……

💩丑陋的

前几天,我碰到了杰克逊·邓斯坦(Jackson Dunstan)的这篇博客文章。 它很好地说明了在Unity中对C#进行编码时在使用委托时无意地创建垃圾是多么容易。 前往Jackson的博客文章并阅读,我会在这里等你。

在阅读博客文章时,我对两件事感到惊讶。 首先,每次调用该方法时,将实例方法传递给需要委托的方法实际上会产生垃圾。 其次,我期望作为委托传递的lambda函数每次使用都会产生垃圾,但是C#编译器实际上为您缓存了lambda函数。 但是,如果lambda函数(或匿名方法)正在使用外部作用域中的变量或数据,则不会对其进行缓存,而是会像实例方法的使用一样在每个调用中创建垃圾。 以下是一些在Unity中使用委托时应避免的提示:

  • 不要将实例方法直接传递给以委托为参数的方法。 有两种选择:1)使用lambda,静态或匿名方法,或者2)您可以将实例方法缓存在变量中并传递该变量。
  • 当依靠C#编译器来缓存您的委托时(直接传递lambda或匿名函数时),则永远不要获取方法自身范围之外的变量。 可以将这些方法视为静态方法(或纯方法/功能方法)。

杰克逊进一步谈论了分支的代价。 如果直接将方法传递给采用委托的方法与将委托缓存在变量中并改为传递该变量之间有区别。 在前者中,将对C#编译器自己的缓存进行检查,我们将始终进行缓存检查(根据Jackson的说法,这可能会损害性能),而在后者中,编译器仍将保留其缓存字段,这意味着存在重复的缓存字段(您和编译器的)。 我认为这两者之间的性能差异通常太小而无法考虑。 当我在计算机上进行基准测试时,我看不出执行时间有很大差异。 因此,在大多数情况下,我个人将不会缓存我的代表,直到它真正成为一个真正的问题。

🤔结论

以这种方式使用委托和扩展方法有很多好处,这使您的代码可重复使用,可读性更高且更简洁。 只要记住如何避免创建意外垃圾即可。 让我知道您在下面的评论中的想法,如果发现您刚刚读到的有趣,请单击那个👏按钮。 如果您对所读内容有任何疑问或评论,也可以在Twitter上打我。