Getting rid of precompiler directives in C#
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
开发者_JAVA技巧 ((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 theBeforeAdd
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.
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?
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);
}
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.
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.
精彩评论