不使用split函数比较版本号

167

如何比较版本号?

例如:

x = 1.23.56.1487.5

y = 1.24.55.487.2


5
你希望得到什么样的结果? - BoltClock
5
如果您想使用具有5部分的版本,而您之前使用的是通常的4部分版本,则可以使用System.Version类。它包括用于比较版本和解析版本字符串的方法。 - psousa
1
x和y的类型是什么? - Gleno
7
每当有人说“不要使用XYZ”时,我总会想知道为什么。为什么你不想使用split函数?如果你不使用System.Version类,split函数似乎是一个很好的方法来做这件事情。 - Bob2Chiv
7个回答

384

你能使用 Version 类吗?
https://learn.microsoft.com/en-us/dotnet/api/system.version

它实现了 IComparable 接口。请注意,如果你使用的是像你展示的这样由5部分组成的版本字符串,这个类可能无法正常工作(那真的是你的版本字符串吗?)。假设你的输入是一个字符串,下面是一个可以正常使用的示例,它使用了通常的 .NET 4 部分版本字符串:

static class Program
{
    static void Main()
    {
        string v1 = "1.23.56.1487";
        string v2 = "1.24.55.487";

        var version1 = new Version(v1);
        var version2 = new Version(v2);

        var result = version1.CompareTo(version2);
        if (result > 0)
            Console.WriteLine("version1 is greater");
        else if (result < 0)
            Console.WriteLine("version2 is greater");
        else
            Console.WriteLine("versions are equal");
        return;

    }
}

5
只有版本由2-4部分组成时才返回已翻译的文本。 - username
@dev_Boston 只有一个例外... 就用这些值 v1=1.0001 和 v2=1.1。它会给我相等的结果。 - Sankar M
11
是的,版本字符串不是十进制字符串,并且在版本号的某个部分前添加零是无关紧要的。换句话说,在版本字符串的第二部分中,“00001”等于“1”。 - JohnD
13
您可以使用“Version.Parse(v1) < Version.Parse(v2)”进行更易读的比较,因为已实现了“operator >(Version v1,Version v2)”操作符。 - Andrey Moiseev
3
请注意,Version.Parse("6.0.0") 小于(<) Version.Parse("6.0.0.0"),它们不相等。 Debug.Assert(new Version("6.0.0") < new Version("6.0.0.0")); - adospace

14
如果您能接受major.minor.build.revision这种版本号格式,您可以使用.Net Version类。否则,您需要实现一些从左到右解析版本号的方法,并持续比对直至发现差异或返回两个版本号相等。

7
除了 @JohnD 的回答之外,可能需要比较部分版本号而不使用 Split('.') 或其他字符串和 int 转换浪费。我刚刚编写了一个 CompareTo 扩展方法,它有一个额外的参数——要比较的版本号的重要部分数(介于 1 和 4 之间)。
public static class VersionExtensions
{
    public static int CompareTo(this Version version, Version otherVersion, int significantParts)
    {
        if(version == null)
        {
            throw new ArgumentNullException("version");
        }
        if(otherVersion == null)
        {
            return 1;
        }

        if(version.Major != otherVersion.Major && significantParts >= 1)
            if(version.Major > otherVersion.Major)
                return 1;
            else
                return -1;

        if(version.Minor != otherVersion.Minor && significantParts >= 2)
            if(version.Minor > otherVersion.Minor)
                return 1;
            else
                return -1;

        if(version.Build != otherVersion.Build && significantParts >= 3)
            if(version.Build > otherVersion.Build)
                return 1;
            else
                return -1;

        if(version.Revision != otherVersion.Revision && significantParts >= 4)
            if(version.Revision > otherVersion.Revision)
                return 1;
            else
                return -1;

        return 0; 
    }
}

5
public int compareVersion(string Version1,string Version2)
    {
        System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(@"([\d]+)");
        System.Text.RegularExpressions.MatchCollection m1 = regex.Matches(Version1);
        System.Text.RegularExpressions.MatchCollection m2 = regex.Matches(Version2);
        int min = Math.Min(m1.Count,m2.Count);
        for(int i=0; i<min;i++)
        {
            if(Convert.ToInt32(m1[i].Value)>Convert.ToInt32(m2[i].Value))
            {
                return 1;
            }
            if(Convert.ToInt32(m1[i].Value)<Convert.ToInt32(m2[i].Value))
            {
                return -1;
            }               
        }
        return 0;
    }

15
请注意,这将导致 compareVersion("1.3", "1.3.1") 返回相等。 - Ozgur Ozcitak

2
这是我的代码。我需要比较一些奇怪的版本字符串,例如“3.2.1.7650.b40”和“3.10.1”,所以我无法使用上面建议的VersionInfo对象。 这只是一个简单的方法,可能不够规范。我也提供了一个简短的函数来测试它。
using System;
                    
public class Program
{
    public static void Main()
    {
        Test_CompareVersionStrings();
    }
    
    /// <summary>
    /// Compare two version strings, e.g.  "3.2.1.0.b40" and "3.10.1.a".
    /// V1 and V2 can have different number of components.
    /// Components must be delimited by dot.
    /// </summary>
    /// <remarks>
    /// This doesn't do any null/empty checks so please don't pass dumb parameters
    /// </remarks>
    /// <param name="v1"></param>
    /// <param name="v2"></param>
    /// <returns>
    /// -1 if v1 is lower version number than v2,
    /// 0 if v1 == v2,
    /// 1 if v1 is higher version number than v2,
    /// -1000 if we couldn't figure it out (something went wrong)
    /// </returns>
    private static int CompareVersionStrings(string v1, string v2)
    {
        int rc = -1000;

        v1 = v1.ToLower();
        v2 = v2.ToLower();

        if (v1 == v2)
            return 0;

        string[] v1parts = v1.Split('.');
        string[] v2parts = v2.Split('.');

        for (int i = 0; i < v1parts.Length; i++)
        {
            if (v2parts.Length < i+1)
                break; // we're done here
            
            string v1Token = v1parts[i];
            string v2Token = v2parts[i];
            
            int x;
            bool v1Numeric = int.TryParse(v1Token, out x);
            bool v2Numeric = int.TryParse(v2Token, out x);
            
            // handle scenario {"2" versus "20"} by prepending zeroes, e.g. it would become {"02" versus "20"}
            if (v1Numeric && v2Numeric) {
                while (v1Token.Length < v2Token.Length)
                    v1Token = "0" + v1Token;
                while (v2Token.Length < v1Token.Length)
                    v2Token = "0" + v2Token;
            }

            rc = String.Compare(v1Token, v2Token, StringComparison.Ordinal);
            //Console.WriteLine("v1Token=" + v1Token + " v2Token=" + v2Token + " rc=" + rc);
            if (rc != 0)
                break;
        }

        if (rc == 0)
        {
            // catch this scenario: v1="1.0.1" v2="1.0"
            if (v1parts.Length > v2parts.Length)
                rc = 1; // v1 is higher version than v2
            // catch this scenario: v1="1.0" v2="1.0.1"
            else if (v2parts.Length > v1parts.Length)
                rc = -1; // v1 is lower version than v2
        }

        if (rc == 0 || rc == -1000)
            return rc;
        else
            return rc < 0 ? -1 : 1;
    }
    
    private static int _CompareVersionStrings(string v1, string v2)
    {
        int rc = CompareVersionStrings(v1, v2);
        Console.WriteLine("Compare v1: " + v1 + "  v2: " + v2 + "  result: " + rc);
        return rc;
    }

    // for debugging
    private static void Test_CompareVersionStrings()
    {
        bool allPass = true;

        // should be equal
        allPass &= (0 == _CompareVersionStrings("1", "1"));
        allPass &= (0 == _CompareVersionStrings("1.1", "1.1"));
        allPass &= (0 == _CompareVersionStrings("3.3.a20", "3.3.A20"));

        // v1 should be lower
        allPass &= (-1 == _CompareVersionStrings("1", "2"));
        allPass &= (-1 == _CompareVersionStrings("1.0", "1.0.1"));
        allPass &= (-1 == _CompareVersionStrings("1.0", "1.1"));
        allPass &= (-1 == _CompareVersionStrings("1.0.0.3", "1.1"));
        allPass &= (-1 == _CompareVersionStrings("1.2.3.4", "1.2.3.4b"));
        allPass &= (-1 == _CompareVersionStrings("1.2.3.4", "1.2.3.4.b"));
        allPass &= (-1 == _CompareVersionStrings("1.8.0", "20.0.0.0"));
        allPass &= (-1 == _CompareVersionStrings("5.6.0.788.2", "20.0.0.0"));

        // v1 should be higher
        allPass &= (1 == _CompareVersionStrings("2", "1"));
        allPass &= (1 == _CompareVersionStrings("1.0.1", "1.0"));
        allPass &= (1 == _CompareVersionStrings("1.1", "1.0"));
        allPass &= (1 == _CompareVersionStrings("1.1", "1.0.0.3"));
        allPass &= (1 == _CompareVersionStrings("1.2.3.4b", "1.2.3.4"));
        allPass &= (1 == _CompareVersionStrings("1.2.3.4.b", "1.2.3.4")); 
        allPass &= (1 == _CompareVersionStrings("20.0.0.0", "5.6.0.788.2"));

        Console.WriteLine("allPass = " + allPass.ToString());
    }   
}

错误。见 ("1.8.0","20.0.0.0") = -1 和 ("5.6.0.788.2","20.0.0.0") = 1。 - Gray Programmerz
1
谢谢指出。我已经进行了更正,并创建了一个dotnetfiddle链接:https://dotnetfiddle.net/ehGhJT - Jay

1
如果由于某些原因您无法直接使用版本的compare-method(例如在客户端-服务器场景中),则另一种方法是从版本中提取一个长数字,然后将数字彼此进行比较。 但是,该数字需要具有以下格式:主要版本、次要版本和修订版本各两位数字,构建版本四位数字。
如何提取版本号:
var version = Assembly.GetExecutingAssembly().GetName().Version;

long newVersion = version.Major * 1000000000L + 
                   version.Minor * 1000000L + 
                   version.Build * 1000L + 
                   version.Revision;

然后在其他地方你可以直接进行比较:

if(newVersion > installedVersion)
{
  //update code
}

注意:installedVersion是之前提取的长数字。

为了让所有数字都有3个位置,代码应该实际上是:"version.Major * 1000000000L + version.Minor * 1000000L + version.Build * 1000L + version.Revision"。 - Stef Heyenrath
1
@StefHeyenrath 没错,随意根据您自己的需求调整上面的代码。 - Fabian Bigler

1

我在网上找到了这个算法,看起来很有效。

//https://www.geeksforgeeks.org/compare-two-version-numbers/amp/

static int versionCompare(string v1, string v2)
    {
        // vnum stores each numeric
    
        // part of version
    
        int vnum1 = 0, vnum2 = 0;
    
        // loop until both string are
        // processed
    
        for (int i = 0, j = 0; (i < v1.Length || j < v2.Length);)
    
        {
            // storing numeric part of
            // version 1 in vnum1
            while (i < v1.Length && v1[i] != '.')
            {
    
                vnum1 = vnum1 * 10 + (v1[i] - '0');
    
                i++;
            }
            // storing numeric part of
    
            // version 2 in vnum2
    
            while (j < v2.Length && v2[j] != '.')
            {
                vnum2 = vnum2 * 10 + (v2[j] - '0');
                j++;
            }
            if (vnum1 > vnum2)
                return 1;
    
            if (vnum2 > vnum1)
                return -1;
    
            // if equal, reset variables and
    
            // go for next numeric part
            vnum1 = vnum2 = 0;
            i++;
            j++;
        }
    
        return 0;
    
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接