C#字符串运算符重载

9

你们好 -

除了争议性问题外,重载字符串运算符<, >, <= 和 >=的正确方法是什么?

我已尝试了五种不同的方式,但都出现了各种错误 - 我最好的尝试是声明一个部分类并从那里重载,但由于某些原因它无法工作。

namespace System
{
   public partial class String
   {
       public static Boolean operator <(String a, String b)
       {
           return a.CompareTo(b) < 0;
       }

       public static Boolean operator >(String a, String b)
       {
           return a.CompareTo(b) > 0;
       }
   }

}


1
请注意:部分类需存放在相同的程序集(项目)中。 - Markus Bruckner
这些操作符已经为字符串定义了,因此重新定义它们没有意义。如果可能的话,这可能会破坏很多代码。 - Phil1970
这些运算符在C#中未定义,至少是如此。它们存在于VB中。也许这就是OP需要它们的原因。这就是我来这里的原因。 - frenchone
7个回答

18

String是一个封闭的类。你无法从它继承,也没有String的原始源代码,因此无法编译它的部分类。即使你得到了源代码(可能通过Reflector或通过Visual Studio符号下载),你仍然会遇到问题,因为它在运行时是一等公民。

你真的需要将<和>作为字符串上的运算符吗?如果是这样...为什么不使用扩展方法呢?

public static bool IsLessThan(this string a, string b) 
{ 
    return a.CompareTo(b) < 0; 
} 

public static bool IsGreaterThan(this string a, string b) 
{ 
    return a.CompareTo(b) > 0; 
}


// elsewhere...
foo.IsLessThan(bar); // equivalent to foo < bar

5
即使他能够继承它,只有作为继承类型引用的实例才能使用备用运算符。由于运算符不是多态的(它们是重载的,而不是覆盖的),即使将子类型作为 "string" 引用,也会消除备用运算符的功能。 - Adam Robinson
@Adam Robinson:没错。底线是:@ScottSEA 无法做他想做的事情。 - Randolpho
2
我知道你的帖子不是关于扩展方法的,但提供一个链接可能会有用。对于刚接触C#的人来说,它看起来好像被错误地调用了。http://msdn.microsoft.com/en-ca/library/vstudio/bb383977.aspx - jgreep

17

没有办法用自己的代码替换编译器内置的任何行为。你无法覆盖现有的比较、转换和算术运算符等内置操作。这是有意设计的; 它使得其他人能够阅读你的代码并知道 int x = M(); int y = x + 2; 执行的是整数运算,而不是格式化硬盘等其他操作。

你能解释一下为什么想要这样做吗?也许有更好的方法来实现你的需求。


谢谢你的回答。作为一个严格遵循的设计决策,这解释了为什么十年前和至今都不可能使用自定义字符串运算符。 - Lorenz Lo Sauer
1
我想要的是将`String.Concat(Enumerable.Repeat("Hello", 4))`表示为`"Hello" * 4`有人知道是否有可能以任何方式实现吗? - Alexey Khoroshikh
1
@AlexeyKhoroshikh:不,没有任何类型可以定义该运算符。 - Eric Lippert

10

简单来说,你无法修改另一个类的运算符。只有在所有文件中都使用了partial关键字声明,并且在同一个程序集中定义了该类,才能允许使用分部类。


5

你是指 System.String 类吗?在C#中这是不可能的。你无法为现有类添加扩展运算符。虽然这是一个非常受欢迎的功能。


3
  • 你不能为字符串创建一个部分类,因为字符串类本身不是部分类,所以它无法与你的部分类一起使用。

  • String是密封的,所以你不能从它继承然后重载运算符。

简而言之,遗憾的是,你不能做你想做的事情。

我不知道你究竟想做什么,所以无法建议一个好的替代方法。但是,请看看扩展方法,它们通常适用于这种情况。撇开是否应该这样做的问题,你可以向字符串类添加一个名为IsGreaterThan的方法,并根据需要返回true或false。这很好,因为你可以给扩展方法命名,使它的意义清晰,保持现有的运算符不变(无论如何你都没有选择),并允许快速/简单的代码。


1

你不能直接重载>=和<=运算符,但是你可以通过分别重载>和==来实现相同的结果。

你的代码看起来对我来说是正确的,除了你错过了==的重载。

似乎我错了,不过,你总是可以退而求其次使用反射。我认为如果你深入挖掘和破解,你可以通过反射使它在运行时扩展类,因为反射允许在运行时添加函数或交换函数体。

无论这是否可取和良好的实践,我都怀疑。有一个原因,为什么这个类是密封的。做我提到的事情可能会导致在某些情况下出现未定义的行为,因为.NET框架对字符串做出了一些假设。很有可能字符串类会在内部崩溃。


-1

经过10年的发展,你可以使用包装类和隐式转换在某种程度上实现它。
但仅仅因为你可以这样做,并不意味着你应该这样做。

下面是一些代码:

    // implements all interfaces that string does through the field content
    public sealed class StringWrapper : IEnumerable<char>, ICloneable, IComparable, IComparable<string>, IConvertible, IEquatable<string>
    {
        private readonly string content;

        private StringWrapper(string content)
        {
            this.content = content;
        }

        // implicit conversions
        public static implicit operator string(StringWrapper d) => d.content;
        public static implicit operator StringWrapper(string b) => new StringWrapper(b);

        public static bool operator <(StringWrapper lhs, StringWrapper rhs)
        {
            return lhs.content.CompareTo(rhs.content) < 0;
        }

        public static bool operator >(StringWrapper lhs, StringWrapper rhs)
        {
            return lhs.content.CompareTo(rhs.content) > 0;
        }

        // string supports it, why shouldnt we?
        public static StringWrapper operator +(StringWrapper lhs, StringWrapper rhs)
        {
            var sb = new StringBuilder();
            sb.Append(lhs.content);
            sb.Append(rhs.content);
            return sb.ToString();
        }

        // at request of @Alexey Khoroshikh
        public static StringWrapper operator *(StringWrapper lhs, int rhs)
        {
            var sb = new StringBuilder();
            for (int i = 0; i < rhs; i++)
            {
                sb.Append(lhs.content);
            }
            return sb.ToString();
        }

        // other nice thing to have
        public static string[] operator /(StringWrapper lhs, char rhs)
        {
            return lhs.content.Split(rhs);
        }

        public override bool Equals(object obj)
        {
            return (obj is StringWrapper wrapper && content == wrapper.content)
                || (obj is string str && content == str);
        }

        #region auto-generated code through visual studio

        public override int GetHashCode()
        {
            return -1896430574 + EqualityComparer<string>.Default.GetHashCode(content);
        }

        public override string ToString()
        {
            return this.content;
        }

        public object Clone()
        {
            return content.Clone();
        }

        public int CompareTo(string other)
        {
            return content.CompareTo(other);
        }

        public bool Equals(string other)
        {
            return content.Equals(other);
        }

        public IEnumerator<char> GetEnumerator()
        {
            return ((IEnumerable<char>)content).GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return ((System.Collections.IEnumerable)content).GetEnumerator();
        }

        public int CompareTo(object obj)
        {
            return content.CompareTo(obj);
        }

        public TypeCode GetTypeCode()
        {
            return content.GetTypeCode();
        }

        public bool ToBoolean(IFormatProvider provider)
        {
            return ((IConvertible)content).ToBoolean(provider);
        }

        public byte ToByte(IFormatProvider provider)
        {
            return ((IConvertible)content).ToByte(provider);
        }

        public char ToChar(IFormatProvider provider)
        {
            return ((IConvertible)content).ToChar(provider);
        }

        public DateTime ToDateTime(IFormatProvider provider)
        {
            return ((IConvertible)content).ToDateTime(provider);
        }

        public decimal ToDecimal(IFormatProvider provider)
        {
            return ((IConvertible)content).ToDecimal(provider);
        }

        public double ToDouble(IFormatProvider provider)
        {
            return ((IConvertible)content).ToDouble(provider);
        }

        public short ToInt16(IFormatProvider provider)
        {
            return ((IConvertible)content).ToInt16(provider);
        }

        public int ToInt32(IFormatProvider provider)
        {
            return ((IConvertible)content).ToInt32(provider);
        }

        public long ToInt64(IFormatProvider provider)
        {
            return ((IConvertible)content).ToInt64(provider);
        }

        public sbyte ToSByte(IFormatProvider provider)
        {
            return ((IConvertible)content).ToSByte(provider);
        }

        public float ToSingle(IFormatProvider provider)
        {
            return ((IConvertible)content).ToSingle(provider);
        }

        public string ToString(IFormatProvider provider)
        {
            return content.ToString(provider);
        }

        public object ToType(Type conversionType, IFormatProvider provider)
        {
            return ((IConvertible)content).ToType(conversionType, provider);
        }

        public ushort ToUInt16(IFormatProvider provider)
        {
            return ((IConvertible)content).ToUInt16(provider);
        }

        public uint ToUInt32(IFormatProvider provider)
        {
            return ((IConvertible)content).ToUInt32(provider);
        }

        public ulong ToUInt64(IFormatProvider provider)
        {
            return ((IConvertible)content).ToUInt64(provider);
        }

        #endregion auto-generated code through visual studio
    }


1
我简直不敢相信你把这个答案发到了一个十年前的问题上!干得好,我的朋友。 - Scott Baker

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