Discussion:
Conditional compilation
(too old to reply)
Anton Shepelev
2016-05-17 20:23:51 UTC
Permalink
Hello, all

I have to maintain a program which can be compiled
to use different (and not fully compatible) versions
of a 3rd-party API. I am using conditional compila-
tion and a symbol that holds the version of the API,
e.g.:

int msSqlVerInt = GetMsSqlVersion();
Api.SqlVersion msSqlVer;
switch ( msSqlVerInt )
{ case 10: msSqlVer = Api.SqlVersion.MsSql2008; break;
#if api_v8
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
#endif
#if api_v9
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
case 12: msSqlVer = Api.SqlVersion.MsSql2014; break;
#endif
default throw new Exception
( "MsSql version " + msSqlVerInt.ToString() " not supported." );
}

The code above takes care of the fact that newer
versions of the API support more versions of MS SQL
Server. The enumeration element Api.SqlVersion.
MsSql2014, for example, is not defined in versions
earler than the 9th. As more API versions are re-
leased, the code will become increasingly redundant,
each #ifdef clause repeating the contents of the
previous one and adding but a single new element.
Is there a way to express it more elegantly, e.g.:

case 10: msSqlVer = Api.SqlVersion.MsSql2008; break;
#if apiver >= 8
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
#endif
#if apiver >= 9
case 12: msSqlVer = Api.SqlVersion.MsSql2014; break;
#endif

And since it turned out that I must handle minor
versions as well (e.g 8.1, 9.2) my current approach
is barely tolarable.
--
() ascii ribbon campaign - against html e-mail
/\ http://preview.tinyurl.com/qcy6mjc [archived]
Marcel Mueller
2016-05-17 23:03:47 UTC
Permalink
Post by Anton Shepelev
int msSqlVerInt = GetMsSqlVersion();
Api.SqlVersion msSqlVer;
switch ( msSqlVerInt )
{ case 10: msSqlVer = Api.SqlVersion.MsSql2008; break;
#if api_v8
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
#endif
#if api_v9
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
case 12: msSqlVer = Api.SqlVersion.MsSql2014; break;
#endif
default throw new Exception
( "MsSql version " + msSqlVerInt.ToString() " not supported." );
}
The code above takes care of the fact that newer
versions of the API support more versions of MS SQL
Server. The enumeration element Api.SqlVersion.
MsSql2014, for example, is not defined in versions
earler than the 9th.
IMHO the enumeration for the version number is an anti pattern. You just
discovered why.
It is in the nature of version numbers that the domain is infinite. So a
natural number, i.e. integer, maybe with sub revision fields fits best.
At the places where you have to distinguish version numbers, usually
relational operators should be used.


Marcel
Arne Vajhøj
2016-05-18 00:56:49 UTC
Permalink
Post by Marcel Mueller
Post by Anton Shepelev
int msSqlVerInt = GetMsSqlVersion();
Api.SqlVersion msSqlVer;
switch ( msSqlVerInt )
{ case 10: msSqlVer = Api.SqlVersion.MsSql2008; break;
#if api_v8
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
#endif
#if api_v9
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
case 12: msSqlVer = Api.SqlVersion.MsSql2014; break;
#endif
default throw new Exception
( "MsSql version " + msSqlVerInt.ToString() " not supported." );
}
The code above takes care of the fact that newer
versions of the API support more versions of MS SQL
Server. The enumeration element Api.SqlVersion.
MsSql2014, for example, is not defined in versions
earler than the 9th.
IMHO the enumeration for the version number is an anti pattern. You just
discovered why.
It is in the nature of version numbers that the domain is infinite. So a
natural number, i.e. integer, maybe with sub revision fields fits best.
At the places where you have to distinguish version numbers, usually
relational operators should be used.
If going that route (I suggested a completely different approach), then
wrap version number in class that has comparison methods.

If the version numbers follow standard MS/.NET pattern, then
System.Version has what is needed.

Arne
Anton Shepelev
2016-05-18 09:40:25 UTC
Permalink
Post by Marcel Mueller
Post by Anton Shepelev
int msSqlVerInt = GetMsSqlVersion();
Api.SqlVersion msSqlVer;
switch ( msSqlVerInt )
{ case 10: msSqlVer = Api.SqlVersion.MsSql2008; break;
#if api_v8
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
#endif
#if api_v9
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
case 12: msSqlVer = Api.SqlVersion.MsSql2014; break;
#endif
default throw new Exception
( "MsSql version " + msSqlVerInt.ToString() " not supported." );
}
The code above takes care of the fact that newer
versions of the API support more versions of MS SQL
Server. The enumeration element Api.SqlVersion.
MsSql2014, for example, is not defined in versions
earler than the 9th.
IMHO the enumeration for the version number is an anti
pattern. You just discovered why. It is in the na-
ture of version numbers that the domain is infinite.
So a natural number, i.e. integer, maybe with sub re-
vision fields fits best.
Sure, but I have no access to this 3rd-party API and
can't change their architecture. By the way, that enum
also contains versions of Oracle so it is really a mon-
ster. The brands of RDBMS'es can be stored in a enumer-
ation but their versions must be represented by a gener-
ic "version" type or class.
Post by Marcel Mueller
At the places where you have to distinguish version
numbers, usually relational operators should be used.
But can I use comparison with conditional compilation?
--
() ascii ribbon campaign - against html e-mail
/\ http://preview.tinyurl.com/qcy6mjc [archived]
Arne Vajhøj
2016-05-18 00:54:19 UTC
Permalink
Post by Anton Shepelev
I have to maintain a program which can be compiled
to use different (and not fully compatible) versions
of a 3rd-party API. I am using conditional compila-
tion and a symbol that holds the version of the API,
int msSqlVerInt = GetMsSqlVersion();
Api.SqlVersion msSqlVer;
switch ( msSqlVerInt )
{ case 10: msSqlVer = Api.SqlVersion.MsSql2008; break;
#if api_v8
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
#endif
#if api_v9
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
case 12: msSqlVer = Api.SqlVersion.MsSql2014; break;
#endif
default throw new Exception
( "MsSql version " + msSqlVerInt.ToString() " not supported." );
}
The code above takes care of the fact that newer
versions of the API support more versions of MS SQL
Server. The enumeration element Api.SqlVersion.
MsSql2014, for example, is not defined in versions
earler than the 9th. As more API versions are re-
leased, the code will become increasingly redundant,
each #ifdef clause repeating the contents of the
previous one and adding but a single new element.
case 10: msSqlVer = Api.SqlVersion.MsSql2008; break;
#if apiver >= 8
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
#endif
#if apiver >= 9
case 12: msSqlVer = Api.SqlVersion.MsSql2014; break;
#endif
And since it turned out that I must handle minor
versions as well (e.g 8.1, 9.2) my current approach
is barely tolarable.
I would switch from this procedural approach to a
more OO approach.

Instead of saving an enum and later test on that
and do different things depending on value, then
I would assign a new instance of a version specific
handler class to a reference of type interface or
abstract base class. And when later needed to change
behavior then call methods on that reference.
Reuse of functionality in old versions in newer
versions can be done via inheritance.

That only solves the enum extension problem - not the
conditional compilation causing code duplication.

This problem could be solved by having a configuration
mapping from version numbers to handler class
and instantiate the handler class from name.

Different approach, but I think it is the right way.

BTW, the idea is not new. This is how NHibernate does
dialects.

Arne
Anton Shepelev
2016-05-18 11:18:07 UTC
Permalink
Post by Anton Shepelev
I have to maintain a program which can be compiled
to use different (and not fully compatible) versions
of a 3rd-party API. I am using conditional compila-
tion and a symbol that holds the version of the API,
int msSqlVerInt = GetMsSqlVersion();
Api.SqlVersion msSqlVer;
switch ( msSqlVerInt )
{ case 10: msSqlVer = Api.SqlVersion.MsSql2008; break;
#if api_v8
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
#endif
#if api_v9
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
case 12: msSqlVer = Api.SqlVersion.MsSql2014; break;
#endif
default throw new Exception
( "MsSql version " + msSqlVerInt.ToString() " not supported." );
}
The code above takes care of the fact that newer
versions of the API support more versions of MS SQL
Server. The enumeration element Api.SqlVersion.
MsSql2014, for example, is not defined in versions
earler than the 9th. As more API versions are re-
leased, the code will become increasingly redundant,
each #ifdef clause repeating the contents of the
previous one and adding but a single new element.
case 10: msSqlVer = Api.SqlVersion.MsSql2008; break;
#if apiver >= 8
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
#endif
#if apiver >= 9
case 12: msSqlVer = Api.SqlVersion.MsSql2014; break;
#endif
And since it turned out that I must handle minor
versions as well (e.g 8.1, 9.2) my current approach
is barely tolarable.
I would switch from this procedural approach to a more
OO approach.
Instead of saving an enum and later test on that and
do different things depending on value, then I would
assign a new instance of a version specific handler
class to a reference of type interface or abstract
base class. And when later needed to change behavior
then call methods on that reference.
Do you propose to create some VersionSpecificOperations
interface or abstract base class to encapsulate every-
thing that differs from version to version? If I under-
stand you correctly, this method is not suitable in my
case because the 3rd-party API is huge and the version-
dependencies are sprinled all over it, so I would need
many such classes or interfaces in order to keep good
cohesion. Then my code would look like this:

nativeApiObject1.Method1();
nativeApiObject1.Method2();
versionSpecific.Method3( nativeApiObject1 );
nativeApiObject1.Method4();

or if I don't want to pass nativeApiObject as parameter
I shall have to create a version-specific wrapper for
each native class, e.g.:

versionSpecificWrapper = new versionSpecificWrapper1(nativeApiObject1);
...
nativeApiObject1.Method2();
verstionSpecificWrapper.Method3();

or a wrapper for the whole native object:

wrapper = new Wrapper1(nativeApiObject1);
wrapper.Method1();
...

which is no good because the API is very large while the
version-dependent sutff constitues about 0.5 or so per-
cent of its interface (public) code.
Reuse of functionality in old versions in newer ver-
sions can be done via inheritance.
And have the inheritance depth equal to the number of
supported versions?

To complicate things, the 3rd-party API in question is a
COM library :-(
That only solves the enum extension problem ->
But how? Any reference to a nonexistant enum element
won't compile.
-> -- not the conditional compilation causing code du-
plication.
This problem could be solved by having a configuration
mapping from version numbers to handler class and in-
stantiate the handler class from name.
Yes, it is my major problem. What is a configuration
mapping? Do you suggest somehow to determine the ver-
sion-specific class to instantiate from a conditional
compilation symbol?
--
() ascii ribbon campaign - against html e-mail
/\ http://preview.tinyurl.com/qcy6mjc [archived]
Arne Vajhøj
2016-05-30 00:57:13 UTC
Permalink
Post by Anton Shepelev
Post by Anton Shepelev
I have to maintain a program which can be compiled
to use different (and not fully compatible) versions
of a 3rd-party API. I am using conditional compila-
tion and a symbol that holds the version of the API,
case 10: msSqlVer = Api.SqlVersion.MsSql2008; break;
#if apiver >= 8
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
#endif
#if apiver >= 9
case 12: msSqlVer = Api.SqlVersion.MsSql2014; break;
#endif
And since it turned out that I must handle minor
versions as well (e.g 8.1, 9.2) my current approach
is barely tolarable.
I would switch from this procedural approach to a more
OO approach.
Instead of saving an enum and later test on that and
do different things depending on value, then I would
assign a new instance of a version specific handler
class to a reference of type interface or abstract
base class. And when later needed to change behavior
then call methods on that reference.
Do you propose to create some VersionSpecificOperations
interface or abstract base class to encapsulate every-
thing that differs from version to version?
Yes.
Post by Anton Shepelev
If I under-
stand you correctly, this method is not suitable in my
case because the 3rd-party API is huge and the version-
dependencies are sprinled all over it, so I would need
many such classes or interfaces in order to keep good
nativeApiObject1.Method1();
nativeApiObject1.Method2();
versionSpecific.Method3( nativeApiObject1 );
nativeApiObject1.Method4();
or if I don't want to pass nativeApiObject as parameter
I shall have to create a version-specific wrapper for
versionSpecificWrapper = new versionSpecificWrapper1(nativeApiObject1);
...
nativeApiObject1.Method2();
verstionSpecificWrapper.Method3();
wrapper = new Wrapper1(nativeApiObject1);
wrapper.Method1();
...
which is no good because the API is very large while the
version-dependent sutff constitues about 0.5 or so per-
cent of its interface (public) code.
If sending everything through a wrapper class is too much, then
you could instead have capabilities classes.

So instead of:

wrapper.DoSomething();

it would be:

if(capability.SupportSomething())
{
real.DoSomething();
}
Post by Anton Shepelev
Reuse of functionality in old versions in newer ver-
sions can be done via inheritance.
And have the inheritance depth equal to the number of
supported versions?
No.

Number of sub classes equal to number of versions.
Post by Anton Shepelev
To complicate things, the 3rd-party API in question is a
COM library :-(
It does not necessarily mean a problem.

But I am starting to understand that you have a very complex
context, which we obviously do not understand fully.
Post by Anton Shepelev
That only solves the enum extension problem ->
But how? Any reference to a nonexistant enum element
won't compile.
If no enum is used, then nonexistant enum is not a problem.
Post by Anton Shepelev
-> -- not the conditional compilation causing code du-
plication.
This problem could be solved by having a configuration
mapping from version numbers to handler class and in-
stantiate the handler class from name.
Yes, it is my major problem. What is a configuration
mapping? Do you suggest somehow to determine the ver-
sion-specific class to instantiate from a conditional
compilation symbol?
No conditional compilation at all.

If the ask database for version call return an int
then you just need a Dictionary<int,Type> to
translate from the version int to a Type that can
be instantiated.

Arne
Anton Shepelev
2016-06-19 19:05:38 UTC
Permalink
Post by Arne Vajhøj
Reuse of functionality in old versions in new-
er versions can be done via inheritance.
And have the inheritance depth equal to the num-
ber of supported versions?
No.
Number of sub classes equal to number of versions.
But still the class for each new version should in-
herit from the class for the previous version, or
how else will new versions reult the functionality
of old ones?
Post by Arne Vajhøj
To complicate things, the 3rd-party API in ques-
tion is a COM library :-(
It does not necessarily mean a problem.
But I am starting to understand that you have a
very complex context, which we obviously do not
understand fully.
Sorry, I tried to keep this question as narrow as
possible yet to provide all the necessary context.
Post by Arne Vajhøj
That only solves the enum extension problem ->
But how? Any reference to a nonexistant enum
element won't compile.
If no enum is used, then nonexistant enum is not a
problem.
As I indicated in my initial post, the enum belongs
to the Api that I use. That's why I denoted it as
Api.SqlVersion . I must use it in order to connect
the API library to the database.
--
() ascii ribbon campaign - against html e-mail
/\ http://preview.tinyurl.com/qcy6mjc [archived]
Anton Shepelev
2016-05-18 14:07:59 UTC
Permalink
Post by Anton Shepelev
I have to maintain a program which can be compiled
to use different (and not fully compatible) versions
of a 3rd-party API. I am using conditional compila-
tion and a symbol that holds the version of the API,
int msSqlVerInt = GetMsSqlVersion();
Api.SqlVersion msSqlVer;
switch ( msSqlVerInt )
{ case 10: msSqlVer = Api.SqlVersion.MsSql2008; break;
#if api_v8
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
#endif
#if api_v9
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
case 12: msSqlVer = Api.SqlVersion.MsSql2014; break;
#endif
default throw new Exception
( "MsSql version " + msSqlVerInt.ToString() " not supported." );
}
The code above takes care of the fact that newer
versions of the API support more versions of MS SQL
Server. The enumeration element Api.SqlVersion.
MsSql2014, for example, is not defined in versions
earler than the 9th. As more API versions are re-
leased, the code will become increasingly redundant,
each #ifdef clause repeating the contents of the
previous one and adding but a single new element.
case 10: msSqlVer = Api.SqlVersion.MsSql2008; break;
#if apiver >= 8
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
#endif
#if apiver >= 9
case 12: msSqlVer = Api.SqlVersion.MsSql2014; break;
#endif
And since it turned out that I must handle minor
versions as well (e.g 8.1, 9.2) my current approach
is barely tolarable.
I would switch from this procedural approach to a more
OO approach.
I forgot to add in the previous post that in this case I
do not consider the procedural approach inferior: the
value of msSqlVer is used only once in the whole pro-
gram, and that is in that very funciton where it is cal-
cualted, in order to connect to the API. It is a local
variable with a short lifespan and is completely isolat-
ed from the rest of the code.
--
() ascii ribbon campaign - against html e-mail
/\ http://preview.tinyurl.com/qcy6mjc [archived]
Anton Shepelev
2016-05-18 15:25:23 UTC
Permalink
Post by Anton Shepelev
I have to maintain a program which can be compiled
to use different (and not fully compatible) versions
of a 3rd-party API. I am using conditional compila-
tion and a symbol that holds the version of the API,
int msSqlVerInt = GetMsSqlVersion();
Api.SqlVersion msSqlVer;
switch ( msSqlVerInt )
{ case 10: msSqlVer = Api.SqlVersion.MsSql2008; break;
#if api_v8
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
#endif
#if api_v9
case 11: msSqlVer = Api.SqlVersion.MsSql2012; break;
case 12: msSqlVer = Api.SqlVersion.MsSql2014; break;
#endif
default throw new Exception
( "MsSql version " + msSqlVerInt.ToString() " not supported." );
}
The code above takes care of the fact that newer
versions of the API support more versions of MS SQL
Server. The enumeration element Api.SqlVersion.
MsSql2014, for example, is not defined in versions
earler than the 9th. As more API versions are re-
leased, the code will become increasingly redundant,
each #ifdef clause repeating the contents of the
previous one and adding but a single new element.
I have now found a way to manage without conditional
compilation is the case above. I am relying on enum
typecasting and Enum.IsDefined():

// This enum shall contain all the MsSql versions supported by all the API versions:
enum ApiMsSqlVersion { v2005 = 4, v2008 = 6, v2012 = 7, v2014 = 8 };

static bool ServerTypeFromMsSqlVersion( int ver, out SqlVersion serverType, out string errMsg )
{ const string errMsSqlVersion = "MS SQL Server version {0} is not supported {1}.";
const string msgProgram = "in this version of our program";
const string msgApi = "in this version of API";
const int SqlV2005 = 9, SqlV2008 = 10, SqlV2012 = 11, SqlV2014 = 12;
bool ok = false;
errMsg = "";
string reason = "";
serverType = SqlVersion.dst_DB_2;
ApiMsSqlVersion apiMsSqlVer;
switch( ver )
{ case SqlV2005: apiMsSqlVer = ApiMsSqlVersion.v2005; break;
case SqlV2008: apiMsSqlVer = ApiMsSqlVersion.v2008; break;
case SqlV2012: apiMsSqlVer = ApiMsSqlVersion.v2012; break;
case SqlV2014: apiMsSqlVer = ApiMsSqlVersion.v2014; break;
default: reason = msgProgram; goto Error;
}
int versionInt = (int)apiMsSqlVer;
if( !Enum.IsDefined( typeof( SqlVersion ), versionInt ) )
{ reason = msgApi; goto Error; }
serverType = ( SqlVersion )versionInt;
ok = true;
Error:
if( !ok )
{ errMsg = String.Format( errMsSqlVersion, ver, reason ); }
return ok;
}

The main drawback is that adding a new version re-
quires three changes:

a. add new item to ApiMsSqlVersion,
b. add a new constant for MS SQL true version num-
ber (SqlV*),
c. add a case statement,

but that has releived me of supporing minor versions
of the 3rd-party API via conditional compilations,
which is good. I could decrease that to a single
change by getting rid of the constants and the enum
and using an associative array to store the mapping
from the true MS SQL version to its coding in the API:

VersionMap.Add( 9, 4 );
VersionMap.Add( 10, 6 );

but prefer the verbose way because this code will
change rarely.

I forgot that the operator '||' works with conditional
compilation symbols, so that clumsy code in the ini-
tial post may be simplified as follows:

#if api_v8 || api_v9
all that pertains to both versions
#endif
#if api_v9
all that pertains to v9 only
#endif
--
() ascii ribbon campaign - against html e-mail
/\ http://preview.tinyurl.com/qcy6mjc [archived]
Continue reading on narkive:
Loading...