无异常地将字符串转换为 ASCII(如 TryParse)

Convert string to ASCII without exceptions (like TryParse)

我正在为 ASCII 字符串 class 实施 TryParse() 方法。该方法接受一个字符串并将其转换为 C 风格的字符串(即以 null 结尾的 ASCII 字符串)。

我一直只使用 Parse(),使用 ::

转换为 ASCII
public static bool Parse(string s, out byte[] result)
{
    result = null;
    if (s == null || s.Length < 1)
        return false;

    byte[]d = new byte[s.Length + 1]; // Add space for null-terminator
    System.Text.Encoding.ASCII.GetBytes(s).CopyTo(d, 0); 
    // GetBytes can throw exceptions 
    // (so can CopyTo() but I can replace that with a loop)
    result = d;
    return true;
}

但是,由于 TryParse 的想法的一部分是消除异常的开销,并且 GetBytes() 抛出异常,我正在寻找不这样做的不同方法。

也许有类似TryGetbytes()的方法?

或者我们可以推断出标准 .Net 的预期格式 string 并以数学方式执行更改(我对 UTF 编码不太熟悉)?

编辑:我想对于字符串中的非 ASCII 字符,TryParse() 方法应该 return false

编辑:我希望当我开始为此 class 实施 ToString() 方法时,我可能需要在那里做相反的事情。

GetBytes 方法抛出异常,因为您的 Encoding.EncoderFallback 指定它应该抛出异常。

使用 EncoderReplacementFallback 创建一个编码对象以避免无法编码的字符出现异常。

Encoding encodingWithFallback = new ASCIIEncoding() { DecoderFallback = DecoderFallback.ReplacementFallback };
encodingWithFallback.GetBytes("Hɘ££o wor£d!");

这种方式模仿了原始 .NET 值类型的 TryParse 方法:

bool TryEncodingToASCII(string s, out byte[] result)
{
    if (s == null || Regex.IsMatch(s, "[^\x00-\x7F]")) // If a single ASCII character is found, return false.
    {
        result = null;
        return false;
    }
    result = Encoding.ASCII.GetBytes(s); // Convert the string to ASCII bytes.
    return true;
}

根据 the documentation.

Encoding.GetBytes 可能抛出两种可能的异常

ArgumentNullException 很容易避免。对你的输入做一个空检查,你可以确保它永远不会被抛出。

EncoderFallbackException 需要更多调查... Reading the documentation:

A fallback strategy determines how an encoder handles invalid characters or how a decoder handles invalid bytes.

如果我们查看 documentation for ASCII encoding,我们会看到:

It uses replacement fallback to replace each string that it cannot encode and each byte that it cannot decode with a question mark ("?") character.

这意味着它不使用异常回退,因此永远不会抛出 EncoderFallbackException

总而言之,如果您使用 ASCII 编码并确保不传入空字符串,那么调用 GetBytes.

将永远不会抛出异常

两个选项:

您可以完全忽略 Encoding,自己编写循环:

public static bool TryParse(string s, out byte[] result)
{
    result = null;
    // TODO: It's not clear why you don't want to be able to convert an empty string
    if (s == null || s.Length < 1)
    {
        return false;
    }

    byte buffer = new byte[s.Length + 1]; // Add space for null-terminator
    for (int i = 0; i < s.Length; i++)
    {
        char c = s[i];
        if (c > 127)
        {
            return false;
        }
        buffer[i] = (byte) c;
    }
    result = buffer;
    return true;
}

这很简单,但可能比使用 Encoding.GetBytes 稍慢。

第二种选择是使用自定义 EncoderFallback:

public static bool TryParse(string s, out byte[] result)
{
    result = null;
    // TODO: It's not clear why you don't want to be able to convert an empty string
    if (s == null || s.Length < 1)
    {
        return false;
    }

    var fallback = new CustomFallback();
    var encoding = new ASCIIEncoding { EncoderFallback = fallback };
    byte buffer = new byte[s.Length + 1]; // Add space for null-terminator
    // Use overload of Encoding.GetBytes that writes straight into the buffer
    encoding.GetBytes(s, 0, s.Length, buffer, 0);
    if (fallback.HadErrors)
    {
        return false;
    }
    result = buffer;
    return true;
}

虽然这需要编写 CustomFallback - 它需要基本上跟踪是否曾被要求处理无效输入。

如果您不介意编码处理两次数据,您可以使用基于 UTF-8 的编码调用 Encoding.GetByteCount 并使用替换回退(使用非 ASCII 替换字符),然后检查returns 字节数是否与字符串中的字符数相同。如果是,请调用 Encoding.ASCII.GetBytes.

我个人会选择第一个选项,除非您有理由相信它太慢。