Connection String Decorating I found myself in a situation where it is necessary to often decorate connection strings with some kind of metadata, like an extra DB Provider attribute, read it back, and remove the decoration without damaging original content.

Here is how it looks like:

        var cs = new MsSqlConnectionStringBuilder 
        { User = "dmitry", Password = "Password!" }
        .Build();

        Console.WriteLine(cs); // Password=Password!;User=dmitry;

        cs = cs.With("Provider", "MSSQL"); 
        Console.WriteLine(cs); // Provider=MSSQL;Password=Password!;User=dmitry;
        Console.WriteLine(cs["Provider"]); // MSSQL

        cs = cs.Excerpt("Provider"); // Password=Password!;User=dmitry;
        Console.WriteLine(cs);


Where custom MsSqlConnectionStringBuilder is:

public class MsSqlConnectionStringBuilder
{
    public string User { get; set; } = "sa";
    public string Password { get; set; }

    public ConnectionString Build() =&gt;
        ConnectionString.Empty
            .With("User", User ?? throw new InvalidOperationException())
            .With("Password", Password ?? throw new InvalidOperationException());
}


And library code is:

public sealed class ConnectionString
{
    public static explicit operator ConnectionString(string text) =&gt; Parse(text);
    public static implicit operator string(ConnectionString connectionString) =&gt; connectionString.ToString();

    public static readonly ConnectionString Empty = new ConnectionString(ConnectionParameter.None);

    public static ConnectionString Parse(string text, string regex = "(?&lt;name&gt;[^=]+)=(?&lt;value&gt;[^;]);?") =&gt;
        new ConnectionString(
            new Regex(regex ?? throw new ArgumentNullException(nameof(regex)))
                .Matches(text ?? throw new ArgumentNullException(nameof(text)))
                .Select(m =&gt; new ConnectionParameter(m.Groups["name"].Value, m.Groups["value"].Value)));

    ConnectionString(IEnumerable&lt;ConnectionParameter&gt; parameters)
    {
        Parameters = parameters.ToArray(); 
    }

    IEnumerable&lt;ConnectionParameter&gt; Parameters { get; }

    public string this[string name] =&gt; Parameters
        .FirstOrDefault(p =&gt; p.Name.ToUpper() == name.ToUpper())?.Value;

    public ConnectionString With(string name, object value) =&gt;
        new ConnectionString(Parameters
            .Prepend(new ConnectionParameter(name, $"{value}")));

    public ConnectionString Excerpt(string name) =&gt;
        new ConnectionString(Enumerable.Concat(
            Parameters.TakeWhile(p =&gt; p.Name.ToUpper() != name.ToUpper()),                
            Parameters.SkipWhile(p =&gt; p.Name.ToUpper() != name.ToUpper()).Skip(1)));

    public override string ToString() =&gt; ToString("{0}={1};");

    public string ToString(string format) =&gt;
        string.Join("", from p in Parameters
                        select string.Format(format, p.Name, p.Value));
}


And:

sealed class ConnectionParameter
{
    public static readonly ConnectionParameter[] None = new ConnectionParameter[0];

    internal ConnectionParameter(string name, string value)
    {
        Name = name ?? throw new ArgumentNullException(nameof(name));
        Value = value ?? throw new ArgumentNullException(nameof(value));
    }

    public string Name { get; }
    public string Value { get; }
}
What struck me most are these lines:


p.Name.ToUpper() != name.ToUpper()



I find it's always better to use the Equals overload that accepts a StringComparer and set it as a readonly field for the entire class rather then hoping you don't forget ToUpper everywhere.



This is a minor thing but I find that Excerpt should be called Without because it does not actually create an excerpt where you can say what you want to have but rather the other way around. The parameter says what should be excluded from the string. I also think it should take a params string[] argument. 




new Regex(regex ?? throw new ArgumentNullException(nameof(regex)))



I find newing a Regex should be reserved for initializing a field. In a situation like here the static Regex.Matches would be more suitable - unless there is a reason why you chose new over the static method.




public string ToString(string format) =&gt;



This one I don't like. It's not a standard .net practice. If you want to use a formattable ToString then the class should implement the IFormattable interface correctly... or you should make this one private.

Besides having this helper method to just do


ToString("{0}={1};")



is a little bit overengineered. How many of such formats do you have? A single ToString() would do too.



ConnectionParameter should be ConnectionStringField (or Property) to clearly communicate it belongs to a connection string.



I'm not sure if you know that there is already a SqlConnectionStringBuider and most providers have their own implementations. 

I prefer to write extensions against this type rather than parsing everything myself. It also validates the keys you use so you cannot use anything that isn't supported... well, unless it was your intention and a reason for the custom builder.