C#中将Int类型转换为Char类型并用作字符串的方法 - VB Chr()函数的真正等价物

12

我正在尝试找到一个明确的答案解决我的问题,这个问题并不是网站上其他问题的重复。我已经阅读了许多关于这个问题的帖子和相关问题,包括这篇文章,它是关键答案之一(许多其他问题被标记为重复,并重定向到这篇文章):What's the equivalent of VB's Asc() and Chr() functions in C#?

我正在将VBA宏转换为C#。在VBA中,chr(7)可以简单地连接到string中,就好像chr()会产生一个string一样。为什么在C#中不能这样做呢?

可惜的是答案并不清楚,有很多次他们说这是正确的用法:

string mystring=(char)7;

然而,由于它不能被解析为字符串,它给我带来了编译器错误。

我不得不使用以下方法使其正常工作:

string mystring=((char)7).ToString();
这相当于VB中的Chr()函数,因为VB中的Chr()评估为字符串。
我的问题是:我是否总是需要显式地将char转换为string,还是有一些情况可以隐式转换?
更新:
根据@Dirk的答案,这也可以工作:
string mystring = "" + (char)7;

这并不减弱我的神秘感。如果连接起来可以运行,为什么没有隐式转换??

我想全面了解VB Chr()与其在C#中的等效之处。我会感激任何能提供参考资料或示例的帮助。先谢谢了。


一行代码没有给编译器错误,只有在构建时才出现了问题,这让我开始寻找答案。这行代码是:if (oWordDoc.ActiveWindow.Selection.Text.EndsWith((char)7) 当我查看了很多帖子后,发现我的问题与其他人的相同。这就是我想要澄清并确保我理解正确的内容:如果想将其用作字符串,则必须始终进行字符串转换。 - ib11
2
根据MSDN(https://msdn.microsoft.com/en-us/library/613dxh46(v=vs.90).aspx),`Chr`返回一个`Char`。VB是否可能有从`Char`到`String`的隐式转换? - yaakov
1
“没有编译器错误,只是在构建时出现问题。” 编译器和构建时间有什么区别? - yaakov
你为什么要提到 Chr()?你是在翻译VB6的代码还是其他的东西吗?(char)7表示:将一个整数值对65536取余,作为UTF-16码单元转换为UTF-16码单元所使用的自然数据类型(char)。(string是一个由UTF-16码单元计算的序列。) - Tom Blodget
@TomBlodget -- 谢谢。我更新了问题。我提供赏金是为了让任何人,包括新手都能够完全理解这个问题。 - ib11
显示剩余4条评论
4个回答

24
你提出了一个有风险的问题。 Chr() 是 VB.NET 中的一个遗留函数,任何现代代码应该使用 ChrW() 代替。它们的区别在于字符值的解释方式,ChrW() 假定字符代码是 Unicode(W = wide)。而 Chr() 则回滚到上个世纪——没有 Unicode 的石器时代,其中字符要么属于 ASCII 字符集(0..127),要么属于“扩展”字符(128..255)。扩展字符属于一个代码页。许多不同的代码页都在常见使用中。这是一个非常大的灾难,程序无法正确解释由位于不同国家或甚至同一国家的其他机器生成的文本。例如,日本有多个常用代码页,但没有一个占主导地位。结果就会产生乱码
我想你指的是 ChrW(),因为没有人喜欢乱码。对于 C# 也是一样。使用 Char.ToString() 是可以的,另一种方法是使用接受 charstring constructor
  string mystring = new string((char)7, 1);

或者您可能更喜欢更一般的形式:

  public static string ChrW(int code) {
      return new string((char)code, 1);
  }

并不是唯一的方法,使用字面量也是可以的,而且很可能是你更喜欢的方法,因为C#不需要像Chr()这样的帮助函数。 ASCII控制码7是响铃字符,当您将其写入控制台时它会发出哔哔声,您可以使用转义来实现:
  string mystring = "\a";

这段内容与Unix相关,其中包含一些控制字符的转义序列,如"\b"表示退格,"\t"表示制表符,"\r"表示回车,"\n"表示换行。在控制台窗口中删除最后一个键入的字符的经典技巧是Console.Write("\b \b");。应该注意Environment.NewLine属性。至于控制字符,你不需要深入了解。最后,\U和\u格式说明符可以让你编码任何字符。
  string mystring = "\u0007";

从示例中不容易看出,但\u值需要是十六进制的。当您使用来自较高Unicode位平面的代码点时,需要使用\U。


我喜欢你简单易懂的解释方式。我对C#还比较新,这次转换有些困难,所以我正在努力全面掌握它,并希望它足够简单,以便像我这样的其他人不必像我一样搜索那么长时间。 - ib11
2
谢谢。潘多拉的盒子很少有简单的内容。 - Hans Passant
2
不,"也不是C#",这就是我想说的了。SO用户对他们喜欢的编程语言充满热情,这是应该的,他们强烈反感任何类似于“你做不到”或“它更难”的话。这两种语言都很好,只是不同而已。 - Hans Passant
嘿,有个问题:为什么回答上有这么多赞(确实值得的,别误会),但问题却没有一个赞?我总是会给好问题点赞,因为它能引出好的回答。你有什么想法吗? - ib11
我看到这个问题有两个投票,那个杯子比空杯子更有价值。 - Hans Passant
显示剩余3条评论

6

如果出于遗留原因必须使用Chr方法,最好将其用作普通方法

如果您不想导入VisualBasic或想了解它的工作原理,Reflector提供了一个不错的代码示例:

public static char Chr(int CharCode)
{
    char ch;
    if ((CharCode < -32768) || (CharCode > 0xffff))
    {
        throw new ArgumentException(Utils.GetResourceString("Argument_RangeTwoBytes1", new string[] { "CharCode" }));
    }
    if ((CharCode >= 0) && (CharCode <= 0x7f))
    {
        return Convert.ToChar(CharCode);
    }
    try
    {
        int num;
        Encoding encoding = Encoding.GetEncoding(Utils.GetLocaleCodePage());
        if (encoding.IsSingleByte && ((CharCode < 0) || (CharCode > 0xff)))
        {
            throw ExceptionUtils.VbMakeException(5);
        }
        char[] chars = new char[2];
        byte[] bytes = new byte[2];
        Decoder decoder = encoding.GetDecoder();
        if ((CharCode >= 0) && (CharCode <= 0xff))
        {
            bytes[0] = (byte) (CharCode & 0xff);
            num = decoder.GetChars(bytes, 0, 1, chars, 0);
        }
        else
        {
            bytes[0] = (byte) ((CharCode & 0xff00) >> 8);
            bytes[1] = (byte) (CharCode & 0xff);
            num = decoder.GetChars(bytes, 0, 2, chars, 0);
        }
        ch = chars[0];
    }
    catch (Exception exception)
    {
        throw exception;
    }
    return ch;
}

对于ASCII字符,它只调用Convert.ToChar,这相当于(char)CharCode。第一件有趣的事情是调用Utils.GetLocaleCodePage

internal static int GetLocaleCodePage()
{
    return Thread.CurrentThread.CurrentCulture.TextInfo.ANSICodePage;
}

虽然人们可能会认为它与Encoding.Default相同,但它创建的编码与当前线程的文化相关联,而不是系统。其余部分只是将代码填入数组并使用编码进行解码。
这种方法有一个主要的限制,在处理编码时通常都是如此 - 它严重依赖于当前区域设置,更改当前线程的文化会破坏所有ASCII之外的代码的转换。但是,如果那就是你想做的事情,这里有一个粗略而简短的等效方法:
public static char Chr(int code)
{
    var encoding = Encoding.GetEncoding(Thread.CurrentThread.CurrentCulture.TextInfo.ANSICodePage);
    return encoding.GetChars(BitConverter.GetBytes((ushort)code))[0];
}

这个方法缺少一些检查,特别是单字节和范围检查。

然后在VB.NET中有一个更简单、更好的方法 - ChrW用于Unicode:

public static char ChrW(int CharCode)
{
    if ((CharCode < -32768) || (CharCode > 0xffff))
    {
        throw new ArgumentException(Utils.GetResourceString("Argument_RangeTwoBytes1", new string[] { "CharCode" }));
    }
    return Convert.ToChar((int) (CharCode & 0xffff));
}

这又回到了与 ToChar 相关的问题:
public static char ToChar(int value)
{
    if ((value < 0) || (value > 0xffff))
    {
        throw new OverflowException(Environment.GetResourceString("Overflow_Char"));
    }
    return (char) value;
}

如您所见,ChrW 与普通的 char 转换一样... 除了负值!您知道,虽然字符代码必须适合两个字节,但它可能来自有符号或无符号短整型,因此该方法确保它是两种类型的正确数字。如果您想考虑这一点,只需执行 CharCode & 0xffff。
因此,正如您所看到的,Chr 就是 Encoding.GetChars,其中编码是当前线程的编码,而 ChrW 就是 (char)CharCode,除了这两个函数还处理负值。没有其他区别。
关于您问题的原始部分,您不能从 char 转换为 string,因为... 没有可能的转换。它们不互相继承,因此您无法进行强制转换,它们也没有任何用户定义的转换操作符,并且 string 不是原始值类型,因此也没有内置转换。VB.NET 可能允许您这样做,但总的来说,由于其古老版本,它允许更多更糟糕的事情。
简而言之,(char) 等同于 Chr 吗?仅对 ASCII 字符代码(0 到 127),否则不是。如果当前编码和代码编码不同,则 Chr 停止工作,这很重要,如果您使用非 ASCII 字符。

4

为了简化语法,以下的AChar类处理转换。

string A = (AChar)65;
Console.WriteLine(A); // output is "A"

以下类表示一个字符,并定义了从ASCII代码页的转换:
```

以下类表示一个字符,并定义了从ASCII代码页的转换:

```
struct AChar
{
    public static implicit operator AChar(char value) => new AChar { Value = value };

    public static explicit operator AChar(string value)
    {
        if (string.IsNullOrEmpty(value))
            return '\x0000';

        if (value.Length > 1)
            throw new InvalidCastException("String contains more than 1 character.");

        return value[0];
    }

    public static explicit operator AChar(long value)
    {
        if(value < 0 || value > 0xFF)
            throw new InvalidCastException("Char code is out of ASCII range.");

        return (AChar)Encoding.ASCII.GetString(new[] { (byte)value });
    }

    public static implicit operator AChar(byte value) => (AChar)(long)value;
    public static explicit operator AChar(int value) => (AChar)(long)value;

    public static implicit operator char(AChar aChar) => aChar.Value;
    public static implicit operator string(AChar aChar) => aChar.Value.ToString();

    public static bool operator==(AChar left, AChar right) =>
        left.Value == right.Value;

    public static bool operator!=(AChar left, AChar right) =>
        left.Value != right.Value;

    public static bool operator >(AChar left, AChar right) =>
        left.Value > right.Value;

    public static bool operator >=(AChar left, AChar right) =>
        left.Value >= right.Value;

    public static bool operator <(AChar left, AChar right) =>
        left.Value < right.Value;

    public static bool operator <=(AChar left, AChar right) =>
        left.Value <= right.Value;

    public override string ToString() => this;

    public override int GetHashCode() =>    
        Value.GetHashCode();

    public override bool Equals(object obj) =>
        obj is AChar && ((AChar)obj).Value == Value;

    char Value { get; set; }
}

首先将您的字符代码转换为 AChar,它与 C# 的 charstring 兼容。


3
其他回答已经非常详细了。这里还有一个 C# 技巧,可以帮助你更好地处理字符:
string mystring = "" + (char)7;

这通常适用于更多不能直接赋值给字符串的类型。这种方法可能对你来说不那么丑陋,并且可以让你在同一行上进行更多的连接操作。


这正是我一直在寻找的。一个字符实际上是字符串的构建块,不能连接起来就没有意义。我知道你不能让一个字符串等于一个字符,需要进行强制转换,但是连接却可以工作!非常感谢。 - ib11
1
很奇怪它没有转换运算符。真的很愚蠢。另外,你可以通过类似的方式让C#做正确的整数除法:(0.0 + 5) / 2将正确地给出2.5,而不是C#编造的愚蠢的2,如果你尝试5/2=2,这甚至不是数学,根据微软的数学巫师。 - Dirk Bester
每个问题都是独立的。不要链接到与此无关的其他问题。这不是一个乞讨网站,你正在搞乱搜索机制。 - Dirk Bester

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接