See the question and my original answer on StackOverflow

Here is a simple parser. It only parses components of the user agent, doesn't match with existing OS or browser names, etc.:

var ua = UserAgent.Parse("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36");

...

public class UserAgent : IEquatable<UserAgent>
{
    public UserAgent()
    {
        System = new HashSet<string>();
        Details = new HashSet<UserAgentPart>();
        Extensions = new HashSet<UserAgentPart>();
    }

    // from here https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
    public virtual UserAgentPart Product { get; set; }
    public virtual ISet<string> System { get; }
    public virtual UserAgentPart Platform { get; set; }
    public virtual ISet<UserAgentPart> Details { get; }
    public virtual ISet<UserAgentPart> Extensions { get; }

    public override int GetHashCode() => base.GetHashCode();
    public override bool Equals(object obj) => Equals(obj as UserAgentPart);
    public bool Equals(UserAgent other) => !ReferenceEquals(other, null) &&
        Equals(Product, other.Product) &&
        Equals(System, other.System) &&
        Equals(Platform, other.Platform) &&
        Equals(Details, other.Details) &&
        Equals(Extensions, other.Extensions);

    public static bool operator !=(UserAgent lhs, UserAgent rhs) => !(lhs == rhs);
    public static bool operator ==(UserAgent lhs, UserAgent rhs)
    {
        if (lhs is null)
        {
            if (rhs is null)
                return true;

            return false;
        }
        return lhs.Equals(rhs);
    }

    public override string ToString()
    {
        var list = new List<string>();
        if (Product != null)
        {
            list.Add(Product.ToString());
        }

        if (System.Count > 0)
        {
            list.Add("(" + string.Join("; ", System) + ")");
        }

        if (Platform != null)
        {
            list.Add(Platform.ToString());
        }

        if (Details.Count > 0)
        {
            list.Add("(" + string.Join("; ", Details) + ")");
        }

        if (Extensions.Count > 0)
        {
            list.Add(string.Join(" ", Extensions));
        }
        return string.Join(" ", list);
    }

    private static bool Equals(ISet<UserAgentPart> parts1, ISet<UserAgentPart> parts2)
    {
        if (parts1.Count != parts2.Count)
            return false;

        foreach (var kv in parts1)
        {
            if (!parts2.Contains(kv))
                return false;
        }
        return true;
    }

    internal static string Nullify(string text)
    {
        if (string.IsNullOrWhiteSpace(text))
            return null;

        text = text.Trim();
        return text.Length == 0 ? null : text;
    }

    public static UserAgent Parse(string text)
    {
        if (string.IsNullOrWhiteSpace(text))
            return null;

        UserAgentPart part;
        var space = text.IndexOf(' ');
        if (space < 0)
        {
            part = UserAgentPart.Parse(text);
            if (part == null)
                return null;

            return new UserAgent { Product = part };
        }

        var product = text.Substring(0, space);
        part = UserAgentPart.Parse(product);
        if (part == null)
            return null;

        var ua = new UserAgent { Product = part };
        var offset = space;

        var startParen = text.IndexOf('(', space + 1);
        if (startParen >= 0)
        {
            var endParen = text.IndexOf(')', startParen + 1);
            if (endParen < 0) // syntax error
                return ua;

            var system = Nullify(text.Substring(startParen + 1, endParen - startParen - 1));
            if (system != null)
            {
                foreach (var sys in system.Split(';'))
                {
                    var syst = Nullify(sys);
                    if (syst != null)
                    {
                        ua.System.Add(syst);
                    }
                }
            }
            offset = endParen;
        }

        var platform = text.IndexOf(' ', offset + 1);
        if (platform < 0)
        {
            ua.Platform = UserAgentPart.Parse(Nullify(text.Substring(offset + 1)));
            return ua;
        }

        startParen = text.IndexOf('(', platform + 1);
        if (startParen >= 0)
        {
            ua.Platform = UserAgentPart.Parse(Nullify(text.Substring(platform + 1)));
            var endParen = text.IndexOf(')', startParen + 1);
            if (endParen < 0) // syntax error
                return ua;

            var details = Nullify(text.Substring(startParen + 1, endParen - startParen - 1));
            if (details != null)
            {
                foreach (var det in details.Split(';'))
                {
                    var dett = UserAgentPart.Parse(Nullify(det));
                    if (dett != null)
                    {
                        ua.Details.Add(dett);
                    }
                }
            }
            offset = endParen;
        }
        else
        {
            space = text.IndexOf(' ', platform + 1);
            if (space < 0)
            {
                ua.Platform = UserAgentPart.Parse(Nullify(text.Substring(platform + 1)));
                return ua;
            }

            ua.Platform = UserAgentPart.Parse(Nullify(text.Substring(platform + 1, space - platform - 1)));
            offset = space;
        }

        foreach (var ext in text.Substring(offset + 1).Split(' '))
        {
            var extt = UserAgentPart.Parse(Nullify(ext));
            if (extt != null)
            {
                ua.Extensions.Add(extt);
            }
        }
        return ua;
    }
}

public class UserAgentPart : IEquatable<UserAgentPart>
{
    public UserAgentPart(string name, string version = null)
    {
        if (name == null)
            throw new ArgumentNullException(nameof(name));

        Name = name;
        Version = version;
    }

    public string Name { get; }
    public string Version { get; }

    public override int GetHashCode()
    {
        var code = Name.GetHashCode();
        if (Version != null)
        {
            code ^= Version.GetHashCode();
        }
        return code;
    }

    public override string ToString() => Version != null ? Name + "/" + Version : Name;
    public override bool Equals(object obj) => Equals(obj as UserAgentPart);
    public bool Equals(UserAgentPart other) => !ReferenceEquals(other, null) && Name == other.Name && Version == other.Version;
    public static bool operator !=(UserAgentPart lhs, UserAgentPart rhs) => !(lhs == rhs);
    public static bool operator ==(UserAgentPart lhs, UserAgentPart rhs)
    {
        if (lhs is null)
        {
            if (rhs is null)
                return true;

            return false;
        }
        return lhs.Equals(rhs);
    }

    public static UserAgentPart Parse(string text)
    {
        if (string.IsNullOrWhiteSpace(text))
            return null;

        var space = text.IndexOf('/');
        if (space < 0)
            return new UserAgentPart(UserAgent.Nullify(text));

        var name = UserAgent.Nullify(text.Substring(0, space));
        if (name == null)
            return null;

        var version = UserAgent.Nullify(text.Substring(space + 1));
        var i = 0;
        for (; i < version.Length; i++)
        {
            var c = version[i];
            if (c != '.' && !char.IsDigit(c))
                break;
        }

        if (i < (version.Length - 1))
        {
            version = version.Substring(0, i);
        }

        return new UserAgentPart(name, UserAgent.Nullify(version));
    }
}