Versions

  • Thread starter Thread starter RedLars
  • Start date Start date
R

RedLars

Hi,

Appreciate some input on a small design issue. Need to represent a
version in an application that come in various formats (1.2.3 or 'a')
and number of octets (1.2 or 1.2.3.4.5).

Considering using an interface as shown below with two implementations
(for now). Lets for simplicity call them VersionNumber and
VersionCharacter.

interface IVersion : IComparable<IVersion>
{

}

Comparing two instances of the same implementation is fairly
straightforward (i.e. compare VersionNumber with VersionNumber).
However comparing a VersionNumber instance with a VersionCharacter is
more difficult IMHO. How do I specify which is greater and how do I
model that? Obviously a third implementation could also appear at some
point. Should such a decision be dealt with internally in each class,
for instance VersionNumber is always greater than another IVersion
type. Then you can get inconsistencies if VersionCharacter has the
opposite rule. So I was thinking about having a central method for
this specific sort of decision but must admit I'm uncertain at this
point. Any other way of solving the issue would be interesting.

Hope I explained the problem properly. If you see any other problems
with the design I’m all ears.

The goal is to have a flexible and maintainable implementation (maybe
asking too much).
 
Hi,

Appreciate some input on a small design issue. Need to represent a
version in an application that come in various formats (1.2.3 or 'a')
and number of octets (1.2 or 1.2.3.4.5).

Considering using an interface as shown below with two implementations
(for now). Lets for simplicity call them VersionNumber and
VersionCharacter.

interface IVersion : IComparable<IVersion>
{

}

Comparing two instances of the same implementation is fairly
straightforward (i.e. compare VersionNumber with VersionNumber).
However comparing a VersionNumber instance with a VersionCharacter is
more difficult IMHO. How do I specify which is greater and how do I
model that?

I would say that compare operation is not common to all versions, but
specific to every version subtype (i.e., the interface should reflect
the fact that you cannot compare versions of two different types). If
you want to capture this requirement in an interface, it is possible
to do so by splitting it in two parts like this:

interface IVersion { /* truly universal methods */ }
interface IVersion<TVersion> : IVersion, IComparable<IVersion>
{ ... }
class VersionNumber : IVersion<VersionNumber> { ... }
class VersionCharacter : IVersion<VersionCharacter> { ... }

This still lets you to retain the genericity where it is typesafe to
do so. For example, a metod that takes two version numbers, and at
some point needs to compare them against each other, but otherwise
doesn't care about their specific types, could be declared like this:

void Foo<T>(IVersion<T> v1, IVersion<T> v2)
{
// call various IVersion members on v1 and v2
...
if (v1.CompareTo(v2) == 0) { ... }
}
 
I would say that compare operation is not common to all versions, but
specific to every version subtype (i.e., the interface should reflect
the fact that you cannot compare versions of two different types). If
you want to capture this requirement in an interface, it is possible
to do so by splitting it in two parts like this:

  interface IVersion { /* truly universal methods */ }
  interface IVersion<TVersion> : IVersion, IComparable<IVersion>
{ ... }
  class VersionNumber : IVersion<VersionNumber> { ... }
  class VersionCharacter : IVersion<VersionCharacter> { ... }

This still lets you to retain the genericity where it is typesafe to
do so. For example, a metod that takes two version numbers, and at
some point needs to compare them against each other, but otherwise
doesn't care about their specific types, could be declared like this:

  void Foo<T>(IVersion<T> v1, IVersion<T> v2)
  {
    // call various IVersion members on v1 and v2
    ...
    if (v1.CompareTo(v2) == 0) { ... }
  }

Thanks for you quick response.

At our company the version syntax has changed over time, so an old
driver might have version 'F' at one point and then skip to version
2.0.0, but this is again different from one product to another. To
cover such case it would be very handy to be able to compare different
IVersion types. Your example would not cover such a case. Maybe I'm
trying to implement too much logic into a simple interface.
 
Then the things you are comparing are "ProductVersions", not Versions. It
does not make sense to compare the versions of two different products if one
might have conceivably gone 1, 2, F, 3, G and the other might have gone 1,
F, 2, G, 3.

Eq.

Any version within our company has only had one change in syntax. So a
typical version history could be A, B, C, D, 2.0.0.0, 2.1.0.0,
3.1.0.0. etc.

Obviously a version could change to a third syntax but I highly doubt
it will ever alternate between syntaxes as shown in your response.

So you might end-up in a scenario where you want to compare A with
3.1.0.0 for a certain piece of equipment. In this case 3.1.0.0 would
be greater.

Hope you understand my requirement.

Cheers
 
Hi Lars,

Lets assume both of the following classes implement IVersion

CVersionTypeAlpha -- class for alpha comparation (A,B)
CVersionTypeNumeric -- (1.2.x, 2.0.x)

So the base rule is .. Alpha older that Numeric. Right?

So make a special check in the Compare methods for this rule. If the 2
parameters of the Compare method are different types, then is Alpha older
the Numeric.

Another way is via a mapping, where the CVersionTypeAlpha class converts all
of its versions to 0.0.0.x, where x is the ordinal value of the character.
E.g. Version "A" is converted to "0.0.0.65". Of course this assumes, that no
version "0.0.0.x" exists and that AlphaVersions are older than
NumericVersions. Its also assumed, that "A" is older that "a". But you can
make it case-in-sensitive. You can also add/subract an offset from the
ordinal value. This really depends on the already used version numbers and
version patterns. But if you can map it to this standard format, then you
are able to do just a CVersionTypeNumeric compare at the end.

Till now i have not read, how you decide what version class you have to
instantiate. I assume you have this already implemented and solved.

Your goal was to get a flexible and maintainable implementation. The problem
is: when the version numbers of your projects are not standardized or follow
different rules, you'll never get a standard implementation for all cases.
You need to implement special cases to deal with it.

-J-
 
Hi Lars,

Lets assume both of the following classes implement IVersion

CVersionTypeAlpha -- class for alpha comparation (A,B)
CVersionTypeNumeric -- (1.2.x, 2.0.x)

So the base rule is .. Alpha older that  Numeric. Right?

So make a special check in the Compare methods for this rule. If the 2
parameters of the Compare method are different types, then is Alpha older
the Numeric.

Another way is via a mapping, where the CVersionTypeAlpha class converts all
of its versions to 0.0.0.x, where x is the ordinal value of the character..
E.g. Version "A" is converted to "0.0.0.65". Of course this assumes, thatno
version "0.0.0.x" exists and that AlphaVersions are older than
NumericVersions. Its also assumed, that "A" is older that "a". But you can
make it case-in-sensitive. You can also add/subract an offset from the
ordinal value. This really depends on the already used version numbers and
version patterns. But if you can map it to this standard format, then you
are able to do just a CVersionTypeNumeric compare at the end.

Till now i have not read, how you decide what version class you have to
instantiate. I assume you have this already implemented and solved.

Your goal was to get a flexible and maintainable implementation. The problem
is: when the version numbers of your projects are not standardized or follow
different rules, you'll never get a standard implementation for all cases..
You need to implement special cases to deal with it.

-J-

Thanks for your input John.

Your last paragraph is spot on. The requirements are a bit non-
standardized. If I wanted to implement both a standardized and a non-
standardized implementation with as little duplicate code as possible
how would I accomplish this?

Say for instance
interface IVersion { /* standardized interface */ }
interface IVersionProprietary { /* non-standardized interface */ }

However this leads to multiple implementations for each interface with
very little difference between them (duplicated code).

As for instantiate the objects I´m considering using a factory that
analysis the string version input and returns the appropriate
interface implementation.
 
I would not add a second interface, because the goal is easy handling of
different versions and that means it should be handled by one interface. But
I wouldn't use interfaces at all. Here is my solution, but it supports only
versions like "A", "B", "C", "1.0.2", "2.0.0.1"... and ofcourse you could
add a interface like IVersion. But at the end you'll need a factory class,
that can create a instance of this interface, and so i decided the left it
out.

The example gives you the ability to create a version object
(CreateObjectFromString) without to know what type of version you have,
compare to version-object by using "if (ver1 < ver2) ..." and has also a
IComparable implementation to get a list sorted correctly.

Simply paste the following code to a program.cs file. It takes me just a few
minutes, so i hope i've done all things right. I used VS2005 for this... and
maybe it suit your needs.

-J-

<code>
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace VersionChecker
{
public class CVersion : IComparable<CVersion>
{
string version;

protected CVersion() { }

public static CVersion CreateObjectFromString(string version)
{
if (String.IsNullOrEmpty(version)) { throw new ArgumentException(); }
if (version.Length == 1 && version[0] >= 'A' && version[0] <= 'Z')
{
return new CVersionAlpha(version);
}
else
{
return new CVersionNumeric(version);
}
}
public string Version
{
get { return version; }
set { version = value; }
}

public static bool operator <(CVersion left, CVersion right)
{
if (left is CVersionAlpha && right is CVersionNumeric) { return true; }
if (left is CVersionNumeric && right is CVersionAlpha) { return false; }
return left.Version.CompareTo(right.Version) < 0;
}
public static bool operator >(CVersion left, CVersion right)
{
if (left is CVersionAlpha && right is CVersionNumeric) { return false; }
if (left is CVersionNumeric && right is CVersionAlpha) { return true; }
return left.Version.CompareTo(right.Version) > 0;
}
public static bool operator ==(CVersion left, CVersion right)
{
return left.Version.CompareTo(right.Version) == 0;
}
public static bool operator !=(CVersion left, CVersion right)
{
return left.Version.CompareTo(right.Version) != 0;
}

#region IComparable<CVersion> Members

public int CompareTo(CVersion other)
{
if (this is CVersionAlpha && other is CVersionNumeric) { return -1; }
if (this is CVersionNumeric && other is CVersionAlpha) { return 1; }
return this.Version.CompareTo(other.Version);
}

#endregion
}

public class CVersionAlpha : CVersion
{
public CVersionAlpha(string ver)
{
Version = ver;
}
}
public class CVersionNumeric : CVersion
{
public CVersionNumeric(string ver)
{
Version = ver;
}
}


static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] argv)
{
CVersion c0 = CVersion.CreateObjectFromString("A"); Debug.WriteLine("c0 =
" + c0.Version);
CVersion c1 = CVersion.CreateObjectFromString("C"); Debug.WriteLine("c1 =
" + c1.Version);
CVersion c2 = CVersion.CreateObjectFromString("1.0.1");
Debug.WriteLine("c2 = " + c2.Version);
CVersion c3 = CVersion.CreateObjectFromString("2.0.1");
Debug.WriteLine("c3 = " + c3.Version);

SortedList<CVersion, object> list = new SortedList<CVersion, object>();
list.Add(c3, null);
list.Add(c2, null);
list.Add(c1, null);
list.Add(c0, null);

foreach (CVersion ver in list.Keys)
{
Debug.WriteLine(ver.Version);
}

// now the tests
if (c0 < c1) { Debug.WriteLine("c0 older than c1"); }
if (c0 > c1) { Debug.WriteLine("c0 newer then c1"); }
if (c0 == c1) { Debug.WriteLine("c0 is equal c1"); }
if (c0 != c1) { Debug.WriteLine("c0 is not equal c1"); }

if (c0 < c2) { Debug.WriteLine("c0 older than c2"); }
if (c0 > c2) { Debug.WriteLine("c0 newer then c2"); }
if (c0 == c2) { Debug.WriteLine("c0 is equal c2"); }
if (c0 != c2) { Debug.WriteLine("c0 is not equal c2"); }

if (c3 < c1) { Debug.WriteLine("c3 older than c1"); }
if (c3 > c1) { Debug.WriteLine("c3 newer then c1"); }
if (c3 == c1) { Debug.WriteLine("c3 is equal c1"); }
if (c3 != c1) { Debug.WriteLine("c3 is not equal c1"); }

if (c2 < c3) { Debug.WriteLine("c2 older than c3"); }
if (c2 > c3) { Debug.WriteLine("c2 newer then c3"); }
if (c2 == c3) { Debug.WriteLine("c2 is equal c3"); }
if (c2 != c3) { Debug.WriteLine("c2 is not equal c3"); }
}
}
}
</code>
 
I would not add a second interface, because the goal is easy handling of
different versions and that means it should be handled by one interface. But
I wouldn't use interfaces at all. Here is my solution, but it supports only
versions like "A", "B", "C", "1.0.2", "2.0.0.1"... and ofcourse you could
add a interface like IVersion. But at the end you'll need a factory class,
that can create a instance of this interface, and so i decided the left it
out.

The example gives you the ability to create aversionobject
(CreateObjectFromString) without to know what type ofversionyou have,
compare toversion-object by using "if (ver1 < ver2) ..." and has also a
IComparable implementation to get a list sorted correctly.

Simply paste the following code to a program.cs file. It takes me just a few
minutes, so i hope i've done all things right. I used VS2005 for this... and
maybe it suit your needs.

-J-

<code>
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace VersionChecker
{
 public class CVersion : IComparable<CVersion>
 {
  stringversion;

  protected CVersion() { }

  public static CVersion CreateObjectFromString(stringversion)
  {
   if (String.IsNullOrEmpty(version)) { throw new ArgumentException(); }
   if (version.Length == 1 &&version[0] >= 'A' &&version[0] <= 'Z')
   {
    return new CVersionAlpha(version);
   }
   else
   {
    return new CVersionNumeric(version);
   }
  }
  public stringVersion
  {
   get { returnversion; }
   set {version= value; }
  }

  public static bool operator <(CVersion left, CVersion right)
  {
   if (left is CVersionAlpha && right is CVersionNumeric) { return true; }
   if (left is CVersionNumeric && right is CVersionAlpha) { return false; }
   return left.Version.CompareTo(right.Version) < 0;
  }
  public static bool operator >(CVersion left, CVersion right)
  {
   if (left is CVersionAlpha && right is CVersionNumeric) { return false; }
   if (left is CVersionNumeric && right is CVersionAlpha) { return true; }
   return left.Version.CompareTo(right.Version) > 0;
  }
  public static bool operator ==(CVersion left, CVersion right)
  {
   return left.Version.CompareTo(right.Version) == 0;
  }
  public static bool operator !=(CVersion left, CVersion right)
  {
   return left.Version.CompareTo(right.Version) != 0;
  }

  #region IComparable<CVersion> Members

  public int CompareTo(CVersion other)
  {
   if (this is CVersionAlpha && other is CVersionNumeric) { return -1; }
   if (this is CVersionNumeric && other is CVersionAlpha) { return 1;}
   return this.Version.CompareTo(other.Version);
  }

  #endregion
 }

 public class CVersionAlpha : CVersion
 {
  public CVersionAlpha(string ver)
  {
   Version= ver;
  }
 }
 public class CVersionNumeric : CVersion
 {
  public CVersionNumeric(string ver)
  {
   Version= ver;
  }
 }

 static class Program
 {
  /// <summary>
  /// The main entry point for the application.
  /// </summary>
  [STAThread]
  static void Main(string[] argv)
  {
   CVersion c0 = CVersion.CreateObjectFromString("A"); Debug.WriteLine("c0 =
" + c0.Version);
   CVersion c1 = CVersion.CreateObjectFromString("C"); Debug.WriteLine("c1 =
" + c1.Version);
   CVersion c2 = CVersion.CreateObjectFromString("1.0.1");
Debug.WriteLine("c2 = " + c2.Version);
   CVersion c3 = CVersion.CreateObjectFromString("2.0.1");
Debug.WriteLine("c3 = " + c3.Version);

   SortedList<CVersion, object> list = new SortedList<CVersion, object>();
   list.Add(c3, null);
   list.Add(c2, null);
   list.Add(c1, null);
   list.Add(c0, null);

   foreach (CVersion ver in list.Keys)
   {
    Debug.WriteLine(ver.Version);
   }

   // now the tests
   if (c0 < c1) { Debug.WriteLine("c0 older than c1"); }
   if (c0 > c1) { Debug.WriteLine("c0 newer then c1"); }
   if (c0 == c1) { Debug.WriteLine("c0 is equal c1"); }
   if (c0 != c1) { Debug.WriteLine("c0 is not equal c1"); }

   if (c0 < c2) { Debug.WriteLine("c0 older than c2"); }
   if (c0 > c2) { Debug.WriteLine("c0 newer then c2"); }
   if (c0 == c2) { Debug.WriteLine("c0 is equal c2"); }
   if (c0 != c2) { Debug.WriteLine("c0 is not equal c2"); }

   if (c3 < c1) { Debug.WriteLine("c3 older than c1"); }
   if (c3 > c1) { Debug.WriteLine("c3 newer then c1"); }
   if (c3 == c1) { Debug.WriteLine("c3 is equal c1"); }
   if (c3 != c1) { Debug.WriteLine("c3 is not equal c1"); }

   if (c2 < c3) { Debug.WriteLine("c2 older than c3"); }
   if (c2 > c3) { Debug.WriteLine("c2 newer then c3"); }
   if (c2 == c3) { Debug.WriteLine("c2 is equal c3"); }
   if (c2 != c3) { Debug.WriteLine("c2 is not equal c3"); }
  }
 }}

</code>

Thanks for the feedback.

Appreciate the code snippet but I do not think its entirely correct.
Because the internal verison is represented by string 12.0.0.1 would
be smaller than 2.0.0.1 which is not the case. Also, any particular
reason why you didnt use object.ReferenceEquals in the various
operator methods?

Will probably go with one interface, two implementation and a factory
for creation. Then add the rule inside each implementation that Number
is greater than Alpha.
 
RedLars said:
Appreciate the code snippet but I do not think its entirely
correct.Because the internal verison is represented by string
12.0.0.1 would be smaller than 2.0.0.1 which is not the case.

Sorry, but i have to quote myself .....
:
This really depends on the already used version numbers and
version patterns.
...
... you'll never get a standard implementation for all cases.

Maybe i've read it over, but you have never exactly defined the pattens of
your versions. So for my defined needs, the example works quite well. There
are many ways to get it working the way you want it.

For all of my product versions i use a fixed defined syntax, so i can decide
the version order easily. Mostly i define <Major>.<Minor>.<Sub>.<Patch> and
then use a fixed pattern like "00.00.00.0000", where 0 stands for a
character between 0 - 9. So all versions have the same length and are
comparable easily. But that implies some rules during version allocation. Of
course you could make it more flexible, but that needs more effort which
maybe never pays off. And its just a suggestion, not a "It must be done this
way!".
:
Also, any particular reason why you didnt use
object.ReferenceEquals in the various operator
methods?

I dont see a benefit. Why should i use it?

:
Will probably go with one interface, two implementation and
a factory for creation.

Like i said, you can do it with interfaces or without. You havnt written,
why you'll use them at all. But IMO it makes no difference between using 1
interface and two implementation or using 1 abstract class and two
implementations. But maybe there are other factors you havnt mentioned yet.

-J-
 
Sorry, but i have to quote myself .....


Maybe i've read it over, but you have never exactly defined the pattens of
your versions. So for my defined needs, the example works quite well. There
are many ways to get it working the way you want it.

For all of my product versions i use a fixed defined syntax, so i can decide
the version order easily. Mostly i define <Major>.<Minor>.<Sub>.<Patch> and
then use a fixed pattern like "00.00.00.0000", where 0 stands for a
character between 0 - 9. So all versions have the same length and are
comparable easily. But that implies some rules during version allocation. Of
course you could make it more flexible, but that needs more effort which
maybe never pays off. And its just a suggestion, not a "It must be done this
way!".


I dont see a benefit. Why should i use it?


Like i said, you can do it with interfaces or without. You havnt written,
why you'll use them at all. But IMO it makes no difference between using 1
interface and two implementation or using 1 abstract class and two
implementations. But maybe there are other factors you havnt mentioned yet.

-J-

Agree the two cases (interface + 2 implementations or abstract + 2
implementations) are very similar. The reason for picking interface
ihmo is that I would use different internal representation for
VersionNumber and VersionAlpha which would mean little (if any) common
methods or member variables.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top