10

我听说人们说不应使用代码生成器和 T4 模板。这背后的逻辑是,如果您使用生成器生成代码,那么通过泛型和模板构建代码会有更好更有效的方法。

虽然我稍微同意上面的这个说法,但我还没有真正找到有效的方法来构建可以说例如实例化自己的模板。换句话说,我永远做不到:

return new T();

此外,如果我想根据数据库值生成代码,我发现Microsoft.SqlServer.Management.SMO与 T4 模板结合使用非常适合生成大量代码,而无需复制/粘贴或使用 resharper。

我在泛型中也发现了许多问题,令我震惊的是,有很多开发人员不理解它们。当我为解决方案检查泛型时,有时它会变得复杂,因为 C# 声明你不能做一些在我看来似乎合乎逻辑的事情。

你怎么认为?您更喜欢构建生成器,还是更喜欢使用泛型?另外,泛型能走多远?我对泛型有相当多的了解,但是我总是遇到一些陷阱和陷阱,这导致我求助于 T4 模板。

处理需要大量灵活性的场景的更合适方法是什么?哦,作为这个问题的奖励,关于 C# 和泛型的好资源是什么?

4

15 回答 15

15

你可以做 new T(); 如果你这样做

public class Meh<T>
  where T : new()
{
  public static T CreateOne()
  {
    return new T();
  }
}

至于代码生成器。我每天使用一个,没有任何问题。事实上,我现在正在使用一个:-)

泛型解决一个问题,代码生成器解决另一个问题。例如,使用 UML 编辑器创建业务模型,然后使用持久性代码生成类,就像我一直使用此工具所做的那样,使用泛型无法实现,因为每个持久性类都是完全不同的。

至于泛型的良好来源。最好的当然是Jon Skeet 的书!:-)

于 2009-02-07T18:51:34.067 回答
15

作为 T4 的鼻祖,我不得不为这个问题辩护好几次,你可以想象 :-)

我的信念是,最好的代码生成是使用可重用库产生等效价值的一步。

正如许多其他人所说,维护 DRY 的关键概念永远不会手动更改生成的代码,而是保留在源元数据更改或在代码生成器中发现错误时重新生成的能力。此时生成的代码具有目标代码的许多特征,您不会遇到复制/粘贴类型问题。

一般来说,生成参数化代码生成器(尤其是基于模板的系统)比正确设计一个将使用成本降低到相同水平的高质量基础库要少得多,因此它是一种快速获取从一致性中获取价值并消除重复错误。

然而,我仍然相信最终的系统通常会通过减少总代码来改进。如果不出意外,它的内存占用几乎总是要小得多(尽管人们倾向于认为泛型在这方面是免费的,但他们肯定不是)。

如果您已经使用代码生成器实现了一些价值,那么这通常会为您赢得一些时间、金钱或善意来投资从生成的代码库中获取库。然后,您可以逐步重新设计代码生成器以针对新库并希望生成更少的代码。冲洗并重复。

对我提出的一个有趣的对立点是,就学习曲线而言,丰富、复杂的参数库并不是最简单的事情,尤其是对于那些没有深入沉浸在平台中的人来说。坚持在更简单的基本框架上生成代码会产生冗长的代码,但它通常非常简单且易于阅读。

当然,如果您的生成器中有很多差异和极其丰富的参数化,您可能只是在牺牲产品的复杂性来换取模板的复杂性。这是一个容易滑入的路径,并且可能使维护同样令人头疼 - 请注意这一点。

于 2011-06-04T20:21:00.390 回答
8

生成代码并不邪恶,也没有异味!关键是在正确的时间生成正确的代码。我认为 T4 很棒——我只是偶尔使用它,但当我使用它时非常有帮助。要说,无条件地生成代码是坏的,这简直是疯了!

于 2009-02-07T20:19:13.860 回答
6

在我看来,只要代码生成是您正常构建过程的一部分,而不是您运行一次然后保留其输出的东西,代码生成器就可以了。我添加这个警告是因为如果只使用一次代码生成器并丢弃创建它的数据,那么您只会自动创建大量的 DRY 违规和维护问题;而每次有效地生成代码意味着您用于生成的任何东西都是真正的源代码,并且生成的文件只是中间编译阶段,您应该大多忽略。

Lex 和 yacc 是允许您以有效方式指定功能并从中生成高效代码的工具的经典示例。尝试手工完成他们的工作会延长您的开发时间,并且可能会产生效率较低且可读性较差的代码。虽然您当然可以将 lex 和 yacc 之类的东西直接合并到您的代码中,并在运行时而不是在编译时完成它们的工作,但这肯定会给您的代码增加相当大的复杂性并减慢它的速度。如果您确实需要在运行时更改规范,这可能是值得的,但在大多数正常情况下,使用 lex/yacc 在编译时为您生成代码是一个巨大的胜利。

于 2009-02-07T20:41:35.263 回答
6

如果没有代码生成,Visual Studio 2010 中的大部分内容都是不可能的。实体框架是不可能的。将控件拖放到窗体上的简单行为是不可能的,Linq 也不可能。说不应该使用代码生成是很奇怪的,因为很多人甚至没有考虑它就使用它。

于 2012-04-12T00:40:46.740 回答
4

也许这有点刺耳,但对我来说代码生成有异味。

使用代码生成意味着有许多潜在的共同原则可以用“不要重复自己”的方式来表达。这可能需要更长的时间,但是当您基于包含机制的基础架构最终获得仅包含真正更改的位的类时,这是令人满意的。

至于泛型......不,我没有太多问题。目前唯一不起作用的就是说

List<Animal> a = new List<Animal>();
List<object> o = a;

但即便如此,在下一版本的 C# 中也是可能的。

于 2009-02-07T18:59:32.507 回答
2

代码生成对我来说是解决语言、框架等中发现的许多问题的一种解决方法。它们本身并不是邪恶的,我想说发布一种语言(C#)和框架是非常非常糟糕(即邪恶)的复制和粘贴(交换属性、事件触发、缺少宏)或使用魔法数字(wpf 绑定)。

所以,我哭了,但我使用它们,因为我必须这样做。

于 2011-06-02T08:33:25.327 回答
2

我使用 T4 进行代码生成以及泛型。两者都很好,各有利弊,并且适用于不同的目的。

就我而言,我使用 T4 基于数据库模式生成实体、DAL 和 BLL。但是,DAL 和 BLL 引用了我基于泛型和反射构建的 mini-ORM。所以我认为你可以并排使用它们,只要你保持控制并保持小而简单。

T4 生成静态代码,而泛型是动态的。如果您使用泛型,则使用反射,据说它的性能低于“硬编码”解决方案。当然你可以缓存反射结果。

关于“return new T();”,我使用这样的动态方法:

public class ObjectCreateMethod
    {
    delegate object MethodInvoker();
    MethodInvoker methodHandler = null;

    public ObjectCreateMethod(Type type)
    {
        CreateMethod(type.GetConstructor(Type.EmptyTypes));
    }

    public ObjectCreateMethod(ConstructorInfo target)
    {
        CreateMethod(target);
    }

    void CreateMethod(ConstructorInfo target)
    {
        DynamicMethod dynamic = new DynamicMethod(string.Empty,
                    typeof(object),
                    new Type[0],
                    target.DeclaringType);
        ILGenerator il = dynamic.GetILGenerator();
        il.DeclareLocal(target.DeclaringType);
        il.Emit(OpCodes.Newobj, target);
        il.Emit(OpCodes.Stloc_0);
        il.Emit(OpCodes.Ldloc_0);
        il.Emit(OpCodes.Ret);

        methodHandler = (MethodInvoker)dynamic.CreateDelegate(typeof(MethodInvoker));
    }

    public object CreateInstance()
    {
        return methodHandler();
    }
}

然后,我这样称呼它:

ObjectCreateMethod _MetodoDinamico = new ObjectCreateMethod(info.PropertyType);
object _nuevaEntidad = _MetodoDinamico.CreateInstance();
于 2011-11-22T07:20:38.257 回答
1

更多的代码意味着更多的复杂性。更高的复杂性意味着有更多的地方可以隐藏错误,这意味着更长的修复周期,这反过来意味着整个项目的成本更高。

只要有可能,我宁愿尽量减少代码量以提供等效的功能;理想情况下使用动态(程序化)方法而不是代码生成。反射、属性、方面和泛型为 DRY 策略提供了许多选项,将生成作为最后的手段。

于 2009-02-07T18:55:44.777 回答
1

泛型和代码生成是两个不同的东西。在某些情况下,您可以使用泛型而不是代码生成,而我认为您应该这样做。对于其他情况,代码生成是一种强大的工具。

对于您只需要根据一些数据输入生成代码的所有情况,代码生成都是可行的方法。最明显但绝不是唯一的例子是 Visual Studio 中的表单编辑器。这里输入是设计器数据,输出是代码。在这种情况下,泛型确实没有任何帮助,但是 VS 简单地根据 GUI 布局生成代码是非常好的。

于 2009-02-07T19:20:32.807 回答
1

代码生成器可以被认为是一种代码气味,表明目标语言存在缺陷或缺乏功能。

例如,虽然这里已经说过“持久化的对象不能泛化”,但最好将其视为“C# 中自动持久化其数据的对象不能在 C# 中泛化”,因为我当然可以在 Python 中通过使用各种方法。

但是,可以通过使用 operator[ ](method_name as string) 在静态语言中模拟 Python 方法,该方法根据要求返回函子或字符串。不幸的是,该解决方案并不总是适用,并且返回函子可能不方便。

我要说的一点是,代码生成器表明了所选语言中的缺陷,通过为手头的特定问题提供更方便的专用语法来解决这些缺陷。

于 2009-06-03T02:18:54.373 回答
1

生成代码的复制/粘贴类型(如 ORM 制作)也非常有用......

您可以创建数据库,然后让 ORM 生成以您喜欢的语言表达的该数据库定义的副本。

当您更改原始定义(数据库)时,优势就来了,按编译,ORM(如果您有一个好的)可以重新生成您的定义副本。现在,编译器类型检查器可以检查对数据库的所有引用,并且当您使用不再存在的表或列时,您的代码将无法编译。

想一想:如果我在代码中多次调用一个方法,我不是指的是我最初给这个方法起的名字吗?我一遍又一遍地重复这个名字……语言设计者意识到了这个问题,并提出了“类型安全”作为解决方案。不删除副本(正如 DRY 建议我们应该做的那样),而是检查它们的正确性。

ORM 生成的代码在引用表名和列名时带来了相同的解决方案。不删除副本/引用,而是将数据库定义带入您的(类型安全)语言中,您可以在其中引用类和属性。与编译器类型检查一起,这以类似的方式解决了类似的问题:当您引用过时或拼写错误的表(类)或列(属性)时,保证编译时错误而不是运行时错误。

于 2011-09-28T18:57:31.273 回答
1

引用:我还没有真正找到有效的方法来构建可以说例如实例化自己的模板。换句话说,我永远做不到:

返回新的 T();

public abstract class MehBase<TSelf, TParam1, TParam2>
    where TSelf : MehBase<TSelf, TParam1, TParam2>, new()
{
    public static TSelf CreateOne()
    {
        return new TSelf();
    }
}

public class Meh<TParam1, TParam2> : MehBase<Meh<TParam1, TParam2>, TParam1, TParam2>
{
    public void Proof()
    {
        Meh<TParam1, TParam2> instanceOfSelf1 = Meh<TParam1, TParam2>.CreateOne();
        Meh<int, string> instanceOfSelf2 = Meh<int, string>.CreateOne();
    }
} 
于 2011-09-28T19:15:24.580 回答
0

为什么能够复制/粘贴真的非常快,让它更容易被接受?

这是我能看到的代码生成的唯一理由。

即使生成器提供了您需要的所有灵活性,您仍然必须学习如何使用这种灵活性 - 这是另一个需要学习和测试的层。

即使它在零时间内运行,它仍然会使代码膨胀。

我推出了自己的数据访问类。它知道关于连接、事务、存储过程参数等的一切,我只需要编写所有 ADO.NET 的东西。

自从我不得不编写(甚至查看)其中包含连接对象的任何内容以来已经很长时间了,以至于我很难临时记住语法。

于 2009-02-07T19:11:44.900 回答
0

代码生成,如泛型、模板和其他此类快捷方式,是一个强大的工具。与最强大的工具一样,它增强了用户的善恶能力——它们是不可分割的。

因此,如果您彻底了解您的代码生成器,预测它将产生的所有内容以及原因,并且出于正当理由打算这样做,那么就去做吧。但是不要使用它(或任何其他技术)让你经过一个你不确定你要去哪里或如何到达那里的地方。

有些人认为,如果你解决了当前的问题并实施了一些行为,那么你就是金子。您为下一个开发人员(可能是您自己)留下了多少不透明和不透明的痕迹并不总是很明显。

于 2009-02-07T19:37:12.663 回答