代码之家  ›  专栏  ›  技术社区  ›  jwendl

代码生成器或T4模板,它们真的很邪恶吗?

  •  9
  • jwendl  · 技术社区  · 17 年前

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

    return new T();
    

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

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

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

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

    15 回复  |  直到 14 年前
        1
  •  15
  •   Peter Morris    17 年前

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

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

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

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

    关于泛型的一个好来源。最好的一定是 Jon Skeet's book 当然可以!:-)

        2
  •  15
  •   GarethJ    15 年前

    我相信,在最好的情况下,代码生成是使用可重用库产生同等价值的一个步骤。

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

    然而,我仍然相信,通过减少代码总量,完成的系统通常会得到改进。如果没有其他东西的话,它的内存占用几乎总是要小得多(尽管人们倾向于认为泛型在这方面是免费的,但他们肯定不是)。

    如果您使用代码生成器实现了一些价值,那么这通常会为您赢得一些时间、金钱或商誉,用于从生成的代码库中获取库。然后,您可以增量地重新设计代码生成器,以针对新库,并希望生成更少的代码。冲洗并重复。

    在这篇文章中,我遇到了一个有趣的对比,即丰富、复杂、参数化的库在学习曲线方面并不是最容易的事情,特别是对于那些没有深入了解平台的人来说。坚持在更简单的基本框架上生成代码可能会产生冗长的代码,但它通常非常简单且易于阅读。

    当然,如果生成器中存在大量差异和极其丰富的参数化,那么您可能只是在用模板中的复杂性来权衡产品的复杂性。这是一条很容易进入的途径,维护也同样令人头疼——请注意这一点。

        3
  •  8
  •   rp.    17 年前

        4
  •  6
  •   Sol    17 年前

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

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

        5
  •  6
  •   Jerry Lanphear    14 年前

        6
  •  4
  •   flq    17 年前

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

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

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

    但在下一个版本的C#中,这也将成为可能。

        7
  •  2
  •   greenoldman    15 年前

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

    所以,我哭泣,但我使用它们,因为我不得不这样做。

        8
  •  2
  •   Nathan    14 年前

    我已经使用T4来生成代码和泛型。两者都很好,各有利弊,适合不同的用途。

    关于“returnnewt();”,我使用如下动态方法:

    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();
    
        9
  •  1
  •   Morendil    17 年前

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

        10
  •  1
  •   Brian Rasmussen    17 年前

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

        11
  •  1
  •   Arafangion    17 年前

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

    但是,可以通过使用操作符[](方法名称为string),在静态语言中模拟Python方法,该操作符根据需要返回一个函子或字符串。不幸的是,该解决方案并不总是适用的,并且返回函子可能会带来不便。

    我要说的一点是,代码生成器指出所选语言中的缺陷,通过提供更方便的 当前特定问题的语法。

        12
  •  1
  •   Renko    14 年前

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

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

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

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

        13
  •  1
  •   Renko    14 年前

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

    返回新的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();
        }
    } 
    
        14
  •  0
  •   ChrisA    17 年前

    为什么能够非常非常快速地复制/粘贴会让它更容易被接受?

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

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

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

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

        15
  •  0
  •   dkretz    17 年前

    因此,如果您完全理解代码生成器,预测它将生成的所有内容,以及为什么,并打算让它出于合理的理由这样做,那么就要对它进行测试。但不要用它(或任何其他技巧)让你通过一个你不确定自己要去哪里或如何到达那里的地方。

    有些人认为,如果你解决了当前的问题,实施了一些行为,你就是黄金。你留给下一个开发者(可能是你自己)的线索有多粗糙和不透明并不总是显而易见的