Избавление от директив прекомпилятора в C # - PullRequest
6 голосов
/ 18 марта 2011

Меня попросили сохранить некоторый код, не являющийся устаревшим, как хотелось бы, и он изобилует директивами компилятора, что делает его в значительной степени нечитаемым и почти так же легко обслуживаемым.Показательный пример: директивы

#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.

ОБНОВЛЕНИЕ:
Одед ответил, предлагая удалить директивы компилятора вокруг метода BeforeAdd, перегрузив его.К сожалению, это не сработает, поскольку предполагается, что оба метода переопределяют класс AbstractBusiness, который предоставляет две разные реализации в зависимости от того, какие сборки в конечном итоге будут включены:

protected virtual void BeforeAdd(TEntity entity) {}

или

protected virtual void BeforeAdd(AbstractBusinessEntity entity) {}

Этот код получает свои зависимости от набора библиотек, созданных компанией в прошлом и с тех пор «обновляющихся».Теперь у них есть 4 разные версии этого набора библиотек со встречными пространствами имен и различными реализациями.Все во имя «обратной совместимости» с приложениями, которые используют (очень) старые версии.


ЗАКЛЮЧЕНИЕ

В итоге я выбрал ответ @ Oded, потому чтоэто имеет смысл как общий подход ( KISS и все такое).Я не мог использовать это в этом случае все же;то, что вы видите здесь, является лишь верхушкой айсберга.Я бы не хотел поцеловать этот код, если бы он заплатил мне.

Ответы [ 4 ]

7 голосов
/ 18 марта 2011

В первом случае, похоже, вы могли бы просто иметь несколько перегрузок метода вместо этой конструкции. Разрешение перегрузки должно позаботиться обо всем на этом этапе.

Во втором случае (с использованием директив) - вы можете использовать псевдонимы некоторых из директив и включать все из них, используя псевдоним, где это необходимо. Что происходит, когда все пространства имен включены? Любые коллизии имен?

2 голосов
/ 18 марта 2011

Я бы сказал, что проблема не в этом классе.Этот класс просто симптом.Проблема в базовом классе, который вызывает 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);
}
1 голос
/ 18 марта 2011

Судя по тому, что я вижу, у первоначального разработчика не было никакого чувства наследования и полиморфизма. Это немного сложно понять из кода, но кажется, что LogEntity и AbstractBusinessEntity имеют общие свойства. Существует ли модель наследования или это два совершенно не связанных между собой класса? Если они не связаны, не могли бы вы создать модель наследования или интерфейс, который они оба могут реализовать? Это может помочь, если вы вставили классы.

Короче говоря, я бы не стал тратить свое время, пытаясь работать с этим кодом в его текущей форме. Я бы нашел способ устранить директивы компилятора любой ценой. Это не выглядит полностью спасенным, но это может потребовать некоторых усилий.

0 голосов
/ 18 марта 2011

Я не знаю, практично ли это, но я бы создал ветки в моем DVCS, Mercurial, чтобы справиться с этим.

У меня будет 2 ветки в игре и третьявременно, пока я исправляю ошибки / добавляю общий код.

Вот как я буду создавать первоначальные версии:

              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

Примечание : предполагается, что вы не собираетесь проводить рефакторинг с использованием жестких рук в типовых или общих ветках, если высделайте это, вам, вероятно, лучше в вашей текущей ситуации, по крайней мере, по сравнению с ветвистым образом, как это.Любой такой рефакторинг сделает будущие слияния действительно болезненными.

...