1

I am searching for a way how to avoid verbose overloads in case of a method that 1) will always convert parameter to string and 2) should be available for passing other types as parameter.

As always a picture snippet is worth a thousand words and found the following solution resp. example: (i.e. by using an object parameter, which is cast to IFormattable for passing invariant culture)

public static string AppendHexSuffix(this object hexNumber)
{
    string hexString = (hexNumber as IFormattable)? // Cast as IFormattable to pass inv cul
        .ToString(null, CultureInfo.InvariantCulture) ?? hexNumber.ToString();
    if (!hexString.StartsWith("0x", true, CultureInfo.InvariantCulture)
        && !hexString.EndsWith("h", true, CultureInfo.InvariantCulture))
    {
        hexString += "h"; // Append hex notation suffix, if missing prefix/suffix
    }
    return hexString;
}

Although, as far as I've tested, it seems to work fine (correct me if I'm missing something), the upper code doesn't look particularly straight-forward to me and would prefer a more intuitive-looking solution.

Final Question: is there any other more elegant way how to solve this 1) without using the upper object parameter approach and 2) without explicitly declare an overload for each type?

Note: the upper snippet should be take strictly as an example, since the if-statement part, does make sense only in case a "real" string is passed as parameter.


EDIT: After taking into account the answers + comments I've got, and after some more trials, the following final implementation seems to be best-suited for my case:

/// <summary>
/// Converts any type to string and if hex notation prefix/suffix is missing
/// yet still a valid hex number, then appends the hex suffix
/// </summary>
/// <param name="hexNumber">Takes a string, decimal and other types as parameter</param>
/// <returns>Returns input object content as string with hex notation</returns>
public static string AppendHexSuffix<T>(this T hexNumber)
{
    // Cast as IFormattable to pass hex format ("X") & invariant culture
    var hexString = (hexNumber as IFormattable)?
        .ToString("X", CultureInfo.InvariantCulture).Trim() ?? hexNumber.ToString().Trim();
    int unused;
    if (!hexString.StartsWith("0x", true, CultureInfo.InvariantCulture)
        && !hexString.EndsWith("h", true, CultureInfo.InvariantCulture)
        && int.TryParse( // No hex prefix/suffix, but still a valid hexadecimal number
            hexString, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out unused))
    {
        hexString += "h";
    }
    return hexString;
}

By this, it also should ignore parameters/objects where hex format makes no sense, as pointed out in the comment from @InBetween. Noteworthy mistake: forgot the "X" format for the first .ToString() call.

Community
  • 1
  • 1
Teodor Tite
  • 1,855
  • 3
  • 24
  • 31
  • 2
    `AppendHexSuffix(this T hexNumber) where T : IFormattable`? – stuartd Jan 17 '17 at 16:36
  • 1
    You might consider posting this to [Code Review](http://codereview.stackexchange.com/), since it doesn't seem that you're asking for help solving a problem, per se. – devlin carnate Jan 17 '17 at 16:37
  • 1
    Your current solution will box and value type coming in which is something that can be avoided with generics. Anyhow, what happens if you pass in an object where hex format makes no sense? Neither generics nor your current solution will protect you from that. – InBetween Jan 17 '17 at 16:39
  • @devlin carnate: to you mean also to remove it from SO? – Teodor Tite Jan 17 '17 at 16:41
  • @proGrammar : I'm just saying it might be a better fit on CR rather than SO. – devlin carnate Jan 17 '17 at 16:43
  • @devlin carnate: yes, I think you're right – Teodor Tite Jan 17 '17 at 16:45
  • @InBetween: that's right. The hex part is more an example. I was more looking for a more orthodox solution for avoiding overloads in scenarios like this. – Teodor Tite Jan 17 '17 at 16:48
  • _I would like to avoid specifying each time the type_ - you don't have to, the compiler can infer the type it from the passed variable - but as `string` doesn't implement `IFormattable` it may not be the best solution anyway. – stuartd Jan 17 '17 at 16:53

2 Answers2

1

You can make use of generics and interface parameters here to always go for the best performance available (avoid boxing) and keep your code DRY.

Lets start off with our common code.

private static string appendHexSuffix(this string hexString)
    if (!hexString.StartsWith("0x", true, CultureInfo.InvariantCulture)
        &&
        !hexString.EndsWith("h", true, CultureInfo.InvariantCulture))
    {
        hexString += "h";
    }
    return hexString;
}

Next lets provide two overloads. First, one for IFormattable.

public static string AppendHexSuffix(this IFormattable hexNumber) =>
    appendHexSuffix(hexNumber.ToString(null, CultureInfo.InvariantCulture));

Now our generic for when that method signature doesn't match what we're passing in.

public static string AppendHexSuffix<T>(this T hexNumber) =>
    appendHexSuffix(hexNumber.ToString());

Since T can be implied we won't need to specify it, just pretend there's no generic at all.

A little off topic but I'm questioning if you really want this method to accept any kind of object at all. You may be better off with a generic constraint that specifies each type you want to possibly accept. Or maybe an interface. Read about generic constraints here: https://msdn.microsoft.com/en-us/library/d5x73970.aspx

Now you did ask for how to do this without overloads so I'll add this as well, but don't recommend it.

public static string AppendHexSuffix<T>(this T hexNumber)
{
    var hexString = (hexNumber as IFormattable)?
        .ToString(null, CultureInfo.InvariantCulture) ?? hexNumber.ToString();
    if (!hexString.StartsWith("0x", true, CultureInfo.InvariantCulture)
        && !hexString.EndsWith("h", true, CultureInfo.InvariantCulture))
    {
        hexString += "h"; // Append hex notation suffix, if missing prefix/suffix
    }
    return hexString;
}
Teodor Tite
  • 1,855
  • 3
  • 24
  • 31
Licht
  • 1,079
  • 1
  • 12
  • 27
  • Interesting suggestion. Although overloads are still necessary, at least there are only 2 of them left. – Teodor Tite Jan 18 '17 at 10:16
  • The overloads aren't necessary since with the generic you could use hexNumber is/as IFormattable and then remove the private method. However I prefer the overload approach as it removes a type check/cast from your code. – Licht Jan 18 '17 at 15:09
  • Would be the generics-approach _without overloads_, a better option than the object-approach? – Teodor Tite Jan 18 '17 at 15:24
  • 1
    Absolutely! This lets .Net figure out what .ToString() method to use once during compile instead of every single time you run it. But understand what is actually happening is .Net is making overloads for you. Every time you call to a generic with a new type (T) the compiler creates a non-generic version of the method or class and adds that to your output. This is how we get generics in .Net that are essentially free from a performance perspective. – Licht Jan 18 '17 at 20:35
1

I think you might be better off using regular expressions for something like this.

First, Convert.ToString will already format the object the way you need, so you can replace that code.

To convert value to its string representation, the method tries to call the IConvertible.ToString implementation of value. If value does not implement the IConvertible interface, the method tries to call the IFormattable.ToString implementation of value. If value does not implement the IFormattable interface, the method calls the ToString method of the underlying type of value.

Second, when you need a string to look a certain way, regular expressions are almost always the way to go. They're very fast, too, if you compile them. 99% of the time you want to put a regular expression object in a static variable for this reason. I use Regexr to test regular expressions. It has the additional benefit of gracefully excepting when the string returned is definitely not a hex string.

private static Regex _parser = new Regex(@"(0x)?((\d{2})+)h?", RegexOptions.Compiled);
public static string AppendHexSuffix(this object hexNumber)
{
  var hexString = Convert.ToString(hexNumber);
  var match = _parser.Match(hexString);
  if (!match.Success)
    throw new FormatException("Object cannot be converted to a hex format");
  return match.Groups[2].Value + "h";
}

#if DEBUG
public static void AppendHexSuffixTest()
{
  AppendHexSuffixTest("0x121212", "121212h");
  AppendHexSuffixTest("0x121212h", "121212h");
  AppendHexSuffixTest("121212h", "121212h");
}

public static void AppendHexSuffixTest(object test, string expected)
{
  if (test.AppendHexSuffix() != expected)
    throw new Exception("Unit test failed");
}
#endif 
Robear
  • 986
  • 1
  • 11
  • 15