摆脱 C# 中的预编译器指令

发布于 2024-10-24 01:46:03 字数 1738 浏览 3 评论 0原文

我被要求维护一些不像我想要的那样遗留的代码,并且它充满了编译器指令,使其几乎不可读且几乎可维护。恰当的例子:

#if CONDITION_1
        protected override void BeforeAdd(LogEntity entity)
#else
        protected override void BeforeAdd(AbstractBusinessEntity entity)
#endif
        {
#if CONDITON_1
            entity.DateTimeInsert = DateTime.Now;
#else
            ((LogEntity) entity).DateTimeInsert = DateTime.Now;
#endif
            base.BeforeAdd(entity);
        }

using 指令甚至更漂亮:

#if CONDITION_1
using CompanyName.Configuration;
#endif

#if CONDITION_2||CONDITION_1
using CompanyName.Data;
using CompanyName.Data.SqlBuilders;
#else
using CompanyName.Legacy.Database;
using CompanyName.Legacy.Database.SQLBuilders;
using CompanyName.Legacy.Database.SQLBuilders.parameterTypes;
#endif

我想我应该给出 ConditionalAttribute 尝试了一下,但在这种情况下不太有效

有什么办法可以摆脱这个编译器指令噩梦吗?

该代码是针对 .NET 3.5 编译的。

更新:
Oded 回答说,建议删除 BeforeAdd 方法周围的编译器指令,从而使其重载。不幸的是,这不起作用,因为这两种方法都应该重写 AbstractBusiness 类,该类根据最终包含的程序集提供两种不同的实现:

protected virtual void BeforeAdd(TEntity entity) {}

或者

protected virtual void BeforeAdd(AbstractBusinessEntity entity) {}

此代码从一组库获取其依赖项该公司在过去创建了一段时间,并且从那时起就一直在“升级”。他们现在拥有该组库的 4 个不同版本,具有冲突的命名空间和不同的实现。所有这些都是以与使用(非常)旧版本的应用程序“向后兼容”的名义。


结论

我最终选择了@Oded的答案,因为它作为通用方法最有意义(KISS等等)。但在这种情况下我不能使用它;您在这里看到的只是冰山一角。如果它付钱给我,我不会想亲吻这个代码。

I've been asked to maintain some not-as-legacy-as-I-would-like code, and it is riddled with compiler directives, making it pretty much unreadable and almost as maintainable. Case in point:

#if CONDITION_1
        protected override void BeforeAdd(LogEntity entity)
#else
        protected override void BeforeAdd(AbstractBusinessEntity entity)
#endif
        {
#if CONDITON_1
            entity.DateTimeInsert = DateTime.Now;
#else
            ((LogEntity) entity).DateTimeInsert = DateTime.Now;
#endif
            base.BeforeAdd(entity);
        }

using directives are even prettier:

#if CONDITION_1
using CompanyName.Configuration;
#endif

#if CONDITION_2||CONDITION_1
using CompanyName.Data;
using CompanyName.Data.SqlBuilders;
#else
using CompanyName.Legacy.Database;
using CompanyName.Legacy.Database.SQLBuilders;
using CompanyName.Legacy.Database.SQLBuilders.parameterTypes;
#endif

I thought I'd give the ConditionalAttribute a go but that won't quite work in this situation

Is there any way I can work my way out of this compiler directive nightmare?

The code is compiled against .NET 3.5.

UPDATE:
Oded answered suggesting removing the compiler directives around the BeforeAdd method thus overloading it. Unfortunately that won't work since both methods are supposed to be overriding an AbstractBusiness class which provides two different implementations depending on which assemblies end up being included:

protected virtual void BeforeAdd(TEntity entity) {}

or

protected virtual void BeforeAdd(AbstractBusinessEntity entity) {}

This code gets its dependencies from a set of libraries de company created some time in the past and have been "upgrading" ever since. They now have 4 different versions of that set of libraries with colliding namespaces and differing implementations. All in the name of "backwards compatibility" with applications that use the (very) old versions.


CONCLUSION

I ended up choosing @Oded's answer because it makes the most sense as a general approach (K.I.S.S. and all that). I could not use it in this case though; what you see here is just the tip of the iceberg. I wouldn't want to K.I.S.S. this code if it paid me.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

两个我 2024-10-31 01:46:03

在第一种情况下,看起来您可以简单地对该方法进行多个重载,而不是使用此构造。重载解析此时应该处理好事情。

在第二种情况下(使用指令) - 您可以为某些指令添加别名并包含所有指令,在需要时使用别名。当包含所有命名空间时会发生什么?有名字冲突吗?

In the first case, it looks like you could simply have several overloads of the method instead of this construct. Overload resolution should take care of things at this point.

In the second case (using directives) - you can alias some of the directives and include all of them, using the alias where needed. What happens when all namespaces are included? Any name collisions?

雨落□心尘 2024-10-31 01:46:03

我想说问题不在于这个类。这个类只是一个症状。问题出在调用 BeforeAdd 的基类中。如果您可以在那里重构,那么您将不需要条件编译。

如果名称和命名空间存在冲突,可以使用 using 关键字(不是程序集的关键字)解决此问题。

所以你可以做类似的事情,

using LegacyLogEntity = Some.Fully.Qualified.Namespace.LogEntity;
using SomeOtherLogEntity = Some.Other.Fully.Qualified.Namespace.CurrentLogEntity;

// ..
LegacyLogEntity entity = new LegacyLogEntity();

我也认为问题出在基类中,而不是在这个类本身中。

在这种情况下,您可以通过使用适应或接口来解决这个废话。

我不知道另一个类叫什么,但我们可以说它叫 EntityAggregator。

public interface IEntity {
    DateTime InsertionTime { get; set; }
}

然后在您的聚合器基类中:

protected virtual void BeforeAdd(IEntity entity)
{ // whatever
}

然后在您的子类中:

protected override void BeforeAdd(IEntity entity)
{
    entity.DateTime = DateTime.Now;
    base.BeforeAdd(entity);
}

现在您可以通过实现该接口将其他对象调整为 IEntity。

当我查看这段代码时,我还想到也许您正在使用事件而不是这段代码。

现在,如果您正在谈论多次使用编译,其中代码在两种不同的条件下在两个不同的位置进行编译,那么您可以通过使用部分类来更优雅地做到这一点。

您将 CONDITION_1 代码隔离为如下所示:

// in file WhateverYourClassIs.condition1.cs
#if !CONDITION_1
#error this file should never be included in a build WITHOUT CONDITION_1 set
#endif

public partial class WhateverYourClassIs {
    protected override void BeforeAdd(LogEntity entity) {
        entity.DateTimeInsert = DateTime.Now;
        base.BeforeAdd(entity);
    }
}

// in file WhateverYourClassIs.NotCondition1.cs

#if CONDITION_1
#error this file should never be included in a build WITH CONDITION_1 set
#endif

public partial class WhateverYourClassIs {
    protected override void BeforeAdd(AbstractBusinessEntity entity) {
        ((LogEntity)entity).DateTimeInsert = DateTime.Now;
        base.BeforeAdd(entity);
    }
}

由于代码重复,我不喜欢这种情况。您可以使用 using 关键字来帮助实现此目的:

#if CONDITION_1
using MyAbstractBusinessEntity = LogEntity;
#else
using MyAbstractBusinessEntity = AbstractBusinessEntity;
#endif

// ...

protected override void BeforeAdd(MyAbstractBusinessEntity entity)
{
    // in CONDITION_1, the case is a no-op
    ((LogEntity)entity).DateTimeInsert = DateTime.Now;
    base.BeforeAdd(entity);
}

I'd claim that the problem isn't in this class. This class is just a symptom. The problem is in the base class that's calling BeforeAdd. If you can refactor there, then you won't need the conditional compiles.

If you have conflicting names and namespaces, you can work around that with the using keyword (not the one for assemblies).

So you can do something like

using LegacyLogEntity = Some.Fully.Qualified.Namespace.LogEntity;
using SomeOtherLogEntity = Some.Other.Fully.Qualified.Namespace.CurrentLogEntity;

// ..
LegacyLogEntity entity = new LegacyLogEntity();

I also think that the problem is in the base class, not in this class, per se.

In that event you can get around this nonsense by using either adaptation or interfacing.

I don't know what the other class is called, but let's say that it's called an EntityAggregator.

public interface IEntity {
    DateTime InsertionTime { get; set; }
}

then in your aggregator base class:

protected virtual void BeforeAdd(IEntity entity)
{ // whatever
}

then in your subclass:

protected override void BeforeAdd(IEntity entity)
{
    entity.DateTime = DateTime.Now;
    base.BeforeAdd(entity);
}

Now you can adapt the other objects to be IEntity by implementing that interface.

When I look at this code, it also strikes me that maybe you be using events instead of this code.

Now if you're talking about multiple use compilation, where the code is being compiled in two separate places under two different conditions, then you can do that more gracefully by using partial classes.

You isolate the CONDITION_1 code into something like this:

// in file WhateverYourClassIs.condition1.cs
#if !CONDITION_1
#error this file should never be included in a build WITHOUT CONDITION_1 set
#endif

public partial class WhateverYourClassIs {
    protected override void BeforeAdd(LogEntity entity) {
        entity.DateTimeInsert = DateTime.Now;
        base.BeforeAdd(entity);
    }
}

// in file WhateverYourClassIs.NotCondition1.cs

#if CONDITION_1
#error this file should never be included in a build WITH CONDITION_1 set
#endif

public partial class WhateverYourClassIs {
    protected override void BeforeAdd(AbstractBusinessEntity entity) {
        ((LogEntity)entity).DateTimeInsert = DateTime.Now;
        base.BeforeAdd(entity);
    }
}

I don't like this in this case because of code repetition. You can help this with use of the using keyword:

#if CONDITION_1
using MyAbstractBusinessEntity = LogEntity;
#else
using MyAbstractBusinessEntity = AbstractBusinessEntity;
#endif

// ...

protected override void BeforeAdd(MyAbstractBusinessEntity entity)
{
    // in CONDITION_1, the case is a no-op
    ((LogEntity)entity).DateTimeInsert = DateTime.Now;
    base.BeforeAdd(entity);
}
痕至 2024-10-31 01:46:03

根据我所看到的,原始开发人员似乎没有任何继承和多态性的意识。从代码中看出来有点困难,但似乎 LogEntity 和 AbstractBusinessEntity 具有共同的属性。是否存在继承模型或者它们是两个完全不相关的类?如果它们不相关,您能否创建一个继承模型或它们都可以实现的接口?如果您粘贴课程可能会有所帮助。

长话短说,我不会浪费时间尝试使用当前形式的代码。我会不惜一切代价找到一种消除编译器指令的方法。看起来并不是完全无法挽救,但可能需要一些努力。

Based on what I'm seeing, it seems the original developer didn't have any sense of inheritance and polymorphism. It's a little difficult to tell from the code, but it seems LogEntity and AbstractBusinessEntity share common properties. Is there an inheritance model or are they two completely unrelated classes? If they are unrelated, could you create an inheritance model or an interface they can both implement? It might help if you pasted the classes.

Long story short, I wouldn't waste my time trying to work with that code in its current form. I'd find a way to eliminate the compiler directives, at all costs. It doesn't look to be completely un-salvageable, but it might take some effort.

屋顶上的小猫咪 2024-10-31 01:46:03

我不知道这是否实用,但我会做的是在我的 DVCS、Mercurial 中创建分支来处理这个问题。

当我修复错误/添加常见代码时,我会使用两个分支,并临时使用第三个分支。

以下是我创建初始版本的方式:

              5---6---7         <-- type 1 of library
             /
1---2---3---4
             \
              8---9--10         <-- type 2 of library

仅修复其中一个版本中的错误:

              5---6---7--11     <-- bugfix or change only to type 1
             /
1---2---3---4
             \
              8---9--10

修复常见错误:

              5---6---7--11--13--15    <-- merged into type 1
             /                   /
1---2---3---4--11--12---+-------+      <-- common fix(es)
             \           \
              8---9--10--14            <-- merged into type 2

注意:这假设您不会在任一类型中进行严厉的重构或者常见的分支,如果你这样做,你当前的情况可能会更好,至少与这样的分支方式相比。任何这样的重构都会让未来的合并变得非常痛苦。

I don't know if it is practical, but what I would do would be to create branches in my DVCS, Mercurial, to handle this.

I would have 2 branches in play, and a 3rd temporarily while I fix bugs/add code that is common.

Here's how I would create the initial versions:

              5---6---7         <-- type 1 of library
             /
1---2---3---4
             \
              8---9--10         <-- type 2 of library

To fix bugs in only one of them:

              5---6---7--11     <-- bugfix or change only to type 1
             /
1---2---3---4
             \
              8---9--10

To fix bugs that are common:

              5---6---7--11--13--15    <-- merged into type 1
             /                   /
1---2---3---4--11--12---+-------+      <-- common fix(es)
             \           \
              8---9--10--14            <-- merged into type 2

Note: This assumes you're not going to make heavy-handed refactoring in either type or common branches, if you do that, you're probably better off with your current situation, at least compared to a branchy way like this. Any such refactoring would make future merges really painful.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文