Skip to content

Instantly share code, notes, and snippets.

@vkobel
Created August 7, 2014 14:22
Show Gist options
  • Save vkobel/d7302c0076c64c95ef4b to your computer and use it in GitHub Desktop.
Save vkobel/d7302c0076c64c95ef4b to your computer and use it in GitHub Desktop.
Simple C# extension method to convert a camel case string to underscore notation without any regex
public static class ExtensionMethods {
public static string ToUnderscoreCase(this string str) {
return string.Concat(str.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())).ToLower();
}
}
@IaroslavTitov
Copy link

Very handy, thanks!

@koubaa
Copy link

koubaa commented Feb 26, 2020

thanks!

@goforgold
Copy link

goforgold commented Jun 19, 2020

Very thanks!

Can you please help me change this in a way so IBTest becomes ib_test not i_b_test?

@goforgold
Copy link

I think I found my answer from 'UseSnakeCaseNamingConvention` from EF Core codebase.

public static string ToSnakeCase(this string name)
{
  if (string.IsNullOrEmpty(name))
    return name;

  var builder = new StringBuilder(name.Length + Math.Min(2, name.Length / 5));
  var previousCategory = default(UnicodeCategory?);

  for (var currentIndex = 0; currentIndex < name.Length; currentIndex++)
  {
    var currentChar = name[currentIndex];
    if (currentChar == '_')
    {
      builder.Append('_');
      previousCategory = null;
      continue;
    }

    var currentCategory = char.GetUnicodeCategory(currentChar);
    switch (currentCategory)
    {
      case UnicodeCategory.UppercaseLetter:
      case UnicodeCategory.TitlecaseLetter:
        if (previousCategory == UnicodeCategory.SpaceSeparator ||
            previousCategory == UnicodeCategory.LowercaseLetter ||
            previousCategory != UnicodeCategory.DecimalDigitNumber &&
            previousCategory != null &&
            currentIndex > 0 &&
            currentIndex + 1 < name.Length &&
            char.IsLower(name[currentIndex + 1]))
        {
          builder.Append('_');
        }

        currentChar = char.ToLower(currentChar);
        break;

      case UnicodeCategory.LowercaseLetter:
      case UnicodeCategory.DecimalDigitNumber:
        if (previousCategory == UnicodeCategory.SpaceSeparator)
          builder.Append('_');
        break;

      default:
        if (previousCategory != null)
          previousCategory = UnicodeCategory.SpaceSeparator;
        continue;
    }

    builder.Append(currentChar);
    previousCategory = currentCategory;
  }

  return builder.ToString();
}

@hatami57
Copy link

hatami57 commented Oct 1, 2021

This improved method solved the mentioned problems in the comments:

public static class ExtensionMethods
{
    
    public static string ToUnderscoreCase(this string str) =>
        string.Concat(str.Select((x, i) => (i > 0 && char.IsUpper(x) && (char.IsLower(str[i - 1]) || char.IsLower(str[i + 1])))
            ? "_" + x.ToString() : x.ToString())).ToLower();
}

@peter-perot
Copy link

peter-perot commented Mar 8, 2023

public static string ToUnderscoreCase(this string str) =>
string.Concat(str.Select((x, i) => (i > 0 && char.IsUpper(x) && (char.IsLower(str[i - 1]) || char.IsLower(str[i + 1])))
? "_" + x.ToString() : x.ToString())).ToLower();

This causes an exception with "abcDeFG".

Here the corrected method:

static string ToUnderscoreCase(this string str) =>
    string.Concat(
        str.Select((x, i) =>
            i > 0 && char.IsUpper(x) && (char.IsLower(str[i - 1]) || i < str.Length - 1 && char.IsLower(str[i + 1]))
                ? "_" + x
                : x.ToString())).ToLowerInvariant();

@drizzle-mizzle
Copy link

Easy on eyes solution with custom separator:

public static string ToLowerBySep(this string source, char sep)
{
    var result = new List<char>();
    var chars = source.Replace(" ", "").ToCharArray();

    for (var i = 0; i < chars.Length; i++)
    {
        if (i != 0 && char.IsUpper(chars[i]))
        {
            result.Add(sep);
        }

        result.Add(chars[i]);
    }

    return string.Concat(result).ToLowerInvariant();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment