System.String如何正确包装以防止不区分大小写?

时间:2015-10-09 13:16:36

标签: c# .net string

这个问题关于管理Windows路径名;我仅将其用作不区分大小写的字符串的特定示例。 (如果我现在改变这个例子,那么一大堆评论将毫无意义。)

这可能类似于Possible to create case insensitive string class?,但目前还没有很多讨论。另外,我并不十分关心string所享有的紧密语言集成或System.String的性能优化。

我们说我使用了很多(通常)不区分大小写的Windows路径名(我实际上并不关心实际路径的许多细节,例如\与{{ 1}},/\\\\\网址,file://等相同。一个简单的包装器可能是:

..

是的,可能应该实现 System.String 上的所有/大多数接口;但上述内容似乎足以供讨论之用。

我现在可以写:

sealed class WindowsPathname : IEquatable<WindowsPathname> /* TODO: more interfaces from System.String */
{
    public WindowsPathname(string path)
    {
        if (path == null) throw new ArgumentNullException(nameof(path));
        Value = path;
    }

    public string Value { get; }

    public override int GetHashCode()
    {
        return Value.ToUpperInvariant().GetHashCode();
    }

    public override string ToString()
    {
        return Value.ToString();
    }

    public override bool Equals(object obj)
    {
        var strObj = obj as string;
        if (strObj != null)
            return Equals(new WindowsPathname(strObj));

        var other = obj as WindowsPathname;
        if (other != null)
            return Equals(other);

        return false;
    }
    public bool Equals(WindowsPathname other)
    {
        // A LOT more needs to be done to make Windows pathanames equal.
        // This is just a specific example of the need for a case-insensitive string
        return Value.Equals(other.Value, StringComparison.OrdinalIgnoreCase);
    }
}

这让我可以谈论&#34; var p1 = new WindowsPathname(@"c:\foo.txt"); var p2 = new WindowsPathname(@"C:\FOO.TXT"); bool areEqual = p1.Equals(p2); // true 在我的代码中,而不是像WindowsPathname这样的实现细节。 (是的,此特定的类也可以扩展为处理StringComparison.OrdinalIgnoreCase vs \,以便 c:/foo.txt 等于< em> C:\ FOO.TXT ;但这不是这个问题的重点。)此外,当将实例添加到集合时,此类(带有附加接口)将不区分大小写;没有必要指定/。最后,像这样的特定类也可以更容易地防止&#34;非感觉&#34;诸如将文件系统路径与注册表项进行比较之类的操作。

问题是:这种方法会成功吗?是否有任何严重和/或微妙的缺陷或其他问题&#34;?(再次,与尝试设置不区分大小写的字符串类,而不是管理Windows路径名有关。)

4 个答案:

答案 0 :(得分:8)

我会创建一个包含字符串的不可变结构,将构造函数中的字符串转换为标准大小写(例如小写)。 然后,您还可以添加隐式运算符以简化创建并覆盖比较运算符。 我认为这是实现行为的最简单方法,而且只需要很小的开销(转换仅在构造函数中)。

以下是代码:

public struct CaseInsensitiveString
{
    private readonly string _s;

    public CaseInsensitiveString(string s)
    {
        _s = s.ToLowerInvariant();
    }

    public static implicit operator CaseInsensitiveString(string d)
    {
        return new CaseInsensitiveString(d);
    }

    public override bool Equals(object obj)
    {
        return obj is CaseInsensitiveString && this == (CaseInsensitiveString)obj;
    }

    public override int GetHashCode()
    {
        return _s.GetHashCode();
    }

    public static bool operator ==(CaseInsensitiveString x, CaseInsensitiveString y)
    {
        return x._s == y._s;
    }

    public static bool operator !=(CaseInsensitiveString x, CaseInsensitiveString y)
    {
        return !(x == y);
    }
}

以下是用法:

CaseInsensitiveString a = "STRING";
CaseInsensitiveString b = "string";

// a == b --> true

这也适用于收藏。

答案 1 :(得分:3)

所以你想要一个将字符串转换为对象的东西,如果你将两个字符串转换为这些对象中的两个,你希望能够将这些对象与你自己的关于相等性的规则进行相等比较。两个对象。

在你的例子中它是关于大写和小写的,但它也可能是关于正斜杠和反斜杠,也许你甚至想要定义&#34; word&#34;美元等于$。

假设您将所定义的字符串的子集合中所有可能字符串的集合划分为相等。在那种情况下&#34;你好&#34;将与&#34; HELLO&#34;相同的子集合和&#34; hElLO&#34;。也许&#34; c:\ temp&#34;将与&#34; c:/ TEMP&#34;。

在同一个集合中

如果您可以找到标识您的子集合的内容,那么您可以说属于同一子集合的所有字符串都具有相同的标识符。或者换句话说:您定义的所有字符串都具有相同的标识符。

如果可能,那么比较子集合标识符就足够了。如果两个字符串具有相同的子集合标识符,则它们属于同一个子集合,因此根据我们的等式定义被认为是相等的。

让我们将此标识符称为字符串的规范化值。 CaseInsensitiveString的构造函数可以将输入字符串转换为字符串的规范化值。要检查两个对象是否相等,我们所要做的就是检查它们是否具有相同的标准化值。

字符串规范化的一个例子是:

  • 使字符串小写
  • 使所有斜杠向后斜杠
  • 将所有单词USD转换为$
  • 删除所有千位分隔符,但不包括千位分隔符
  • 等,取决于您希望字符串何时相等。

根据以上所述,以下字符串将导致相同的规范化字符串:

  • 白宫$ 1,000,000
  • 白宫$ 1000000
  • 白宫1000000美元

我们可以将任何东西定义为规范化字符串,只要我们定义的所有字符串都具有相同的规范化字符串。一个很好的例子是

  • 白宫$ 1000000

注意:我没有详细介绍如何找到像USD和千分隔符这样的单词。重要的是你理解规范化字符串的含义。

说完这个,唯一困难的部分就是找到stringIdentifier。课程的其余部分相当简单:

施工规范。构造函数接受一个字符串并确定它所属的子集合。我还添加了一个默认构造函数。

public class CaseInsensitiveString : IEquatable<CaseInsensitiveString>
{
    private string normalized = "";

    public CaseInsensitiveString(string str)
    {
        this.normalized = Normalize(str);
    }

    public CaseInsensitiveString()
    {
         this.Normalize = Normalize(null);
    }
}

平等:根据定义,如果两个对象具有相同的标准化值

,则它们是相同的

请参阅MSDN How to Define Value Equality for a Type

public bool Equals (CaseInsensitiveString other)
{
    // false if other null
    if (other != null) return false;

    // optimization for same object
    if (object.ReferenceEquals(this, other) return true;

    // false if other a different type, for instance subclass
    if (this.Gettype() != other.Gettype()) return false;

    // if here: everything the same, compare the stringIdentifier
    return this.normalized==other.normalized;
}

请注意,最后一行是我们进行实际等式检查的唯一代码!

所有其他相等函数仅使用上面定义的Equals函数:

public override bool Equals(object other)
{
    return this.Equals(other as CaseInsensitiveString);
}

public override int GetHashCode()
{
    return this.Normalized.GetHashCode();
}

public static bool operator ==(CaseInsensitiveString x, CaseInsensitiveString y)
{
    if (object.ReferenceEquals(x, null)
    {   // x is null, true if y also null
        return y==null;
    }
    else
    {   // x is not null
        return x.Equals(y);
    }
}

public static bool operator !=(CaseInsensitiveString x, CaseInsensitiveString y)
{
    return !operator==(x, y);
}

现在您可以执行以下操作:

var x = new CaseInsensitiveString("White House $1,000,000");
var y = new CaseInsensitiveString("white house $1000000");
if (x == y)
    ...

现在我们唯一要实现的是Normalize功能。一旦你知道两个字符串何时被认为是相等的,你就知道如何规范化。

假设两个字符串相等,如果它们是相等的不区分大小写,则正斜杠与反斜杠相同。 (糟糕的英语)

如果normalize函数返回带有所有反斜杠的小写字母相同的字符串,那么我们认为相等的两个字符串将具有相同的标准化值

private string Normalize(string str)
{
    return str.ToLower().Replace('/', '\');
}

答案 2 :(得分:-1)

嗯......我不认为弦乐案例是你唯一的挑战。我来问你几个问题:

c:\myPathc:/myPath相同吗? file:////c:/myPath怎么样?或者\\myMachine\c$\myPath怎么样?

我知道你的目标在哪里以及你想要完成什么,但看起来你似乎已经在一个简单的问题上进行了隧道视觉 - 为什么要构建一个简单.ToLower() vs的框架ToLower()比较吗?

话虽如此,如果您的问题范围除了字符串大小,还涉及尝试评估两个给定路径的绝对相等性,那么编写一个类是有意义的。但这需要比你提出的解决方案更多的解决方案......

HTH!

答案 3 :(得分:-1)

首先,将Spade称为Spade
您必须明确该课程的明确责任。

您希望该类管理Windows路径名,然后您不能放弃所有关于此的注释,因为“代码管理案例”将与“代码管理路径”合并。耦合将使您无法测试(并确保正确的行为)套管而无需路径考虑。

或者你想实现一个 CaseInvariantString 然后你给它充分命名(并且可能在另一个名为WindowsPathname的类中使用它)。

有关课堂响应能力,凝聚力,耦合和其他伟大概念的参考资料,我会推荐以下书籍:

    罗伯特·马丁(鲍勃叔叔)
  • 清洁代码
  • 来自Steeve McConnell的
  • 代码完成

其次,在一个类中包装字符串以检查大小写不变性可以被视为在PositiveInteger类中包装整数。 某些人可以(并且将会)将其视为过度工程。所有开发人员都倾向于尝试达到面向对象教条的顶峰。这里似乎是在类中包装所有值类型的实践(比如int到ID类)。但是,别忘了问你问题。

  • 采用这种做法的成本是多少?
  • 有什么好处?
  • 它可能带来哪些困难?
  • 我可以对所有项目采用一般方法吗?
  • 我是否制定了我的技术主管/建筑师(或类似机构),这是一个好习惯?

最后,作为一个简单的技术点。 您不应在班级内创建字符串。这对性能有害。事实上,因为字符串是不变的,所以当您在ToUpperInvariant()中执行GetHashCode()时,它会创建一个新的String

并且为了路径不变...它在Windows之外不起作用(对于Mono,显然/ foo!= / Foo )。