将 DataColumn 重命名为仅更改大小写会导致奇怪的行为

DataColumn renaming to only change casing causes a weird behaviour

提问人:dabon 提问时间:5/24/2023 最后编辑:Charliefacedabon 更新时间:5/24/2023 访问量:47

问:

当涉及到DataColumn重命名时,我在.NET Framework和.NET中发现了一个奇怪的行为(错误?以下是重现它的方法。

  1. 在 DataTable 中获取 DataColumn
  2. 重命名它,以便仅更改其大小写
  3. 再次将其重命名为其他名称
  4. 现在,该列将同时以旧名称和新名称提供
  5. 如果将其重命名回旧名称,则会收到 DuplicateNameException

该问题似乎与 DataColumnCollection 的内部字典有关,当列重命名为仅更改其大小写时,该字典不会更新。

DataTable table = new DataTable();
table.Columns.Add("foo", typeof(string));
// The DataTable contains a single column named "foo"
// The dictionary _columnFromName contains a single entry having its key equal to "foo"

table.Columns["foo"].ColumnName = "FOO";
// The DataTable now contains a single column named "FOO"
// The dictionary _columnFromName did not get updated, so the entry key STILL equal to "foo"

table.Columns["FOO"].ColumnName = "bar";
// The DataTable now contains a single column named "bar"
// Here the DataColumnCollection registers the new columnname "bar", but fails the unregistration of the column name "FOO", because the dictionary does not contain a key equal to "FOO".
// So, the dictionary _columnFromName now contains TWO entries having their keys equal to "foo" and "bar"

// In fact, the column is still available under the old name ...
Console.WriteLine(table.Columns["foo"]);

// ... and of course it is also available under the new name "bar"
Console.WriteLine(table.Columns["bar"]);
        
// Now, this will throw a DuplicateNameException, because the old dicionary key is still present
table.Columns["bar"].ColumnName = "foo";

下面是面向 .NET Framework 4.7.2 的 .NET Fiddle。您可以将其更改为 .NET 7,但仍会遇到此问题。https://dotnetfiddle.net/vhoV6X

还有其他人遇到过这种行为吗?是故意的吗?Microsoft知道吗?

C# 数据表 System.data

评论

0赞 Steve 5/24/2023
起初,我想到了 DataTable 的 CaseSensitive 属性。默认值为 false,因此我尝试在添加列之前将其设置为 true。但是,将 CaseSensitive 属性设置为 true 时,问题似乎仍然存在。

答:

1赞 Andrew Williamson 5/24/2023 #1

我找不到任何关于这种行为的官方文档,但我的假设是,一旦设置了列的名称,就不应该改变。如果 Microsoft 现在在不需要向后兼容性的情况下重新实现该类,我希望他们会使属性“init”而不是“set”。

假设您可以重命名列。如果数据表已经包含行,会发生什么情况?应该将值移动到新的列名称中,还是保持不变?

一个类似的例子是 。它无法阻止您修改已添加的对象,即使这样做可能会更改哈希值,从而破坏哈希集的功能(尽管这据可查)。HashSet

评论

2赞 Steve 5/24/2023
好的,将列添加到表后更改列的名称有点“不合逻辑”,但问题仍然存在。在 referencesource 中搜索它似乎起源于一个字符串。在 ColumnName 属性内使用设置为 true 的 ignoreCase 参数进行比较。
1赞 Charlieface 5/24/2023
在我看来,这就像一个彻头彻尾的错误。它确实允许您更改名称,并且有大量代码,但它们没有考虑到在列字典中找不到不区分大小写的值。看看我的答案
3赞 Charlieface 5/24/2023 #2

ColumnName setter 的代码使用 with 来检查是否调用 .string.CompareignoreCase: truetable.Columns.RegisterColumnName

if (String.Compare(_columnName, value, true, Locale) != 0) {
// skip... 
    table.Columns.RegisterColumnName(value, this);
    if (_columnName.Length != 0)
        table.Columns.UnregisterName(_columnName);
}

因此,只有当名称以不区分大小写的方式更改时,才会更新表。这会产生一个错误,您可以先将其更改为相似的名称(并且不会发生),然后更改不同的名称(失败,因为它找不到它)。ColumnsUnregisterNameUnregisterName

UnregisterName 函数如下所示:

    internal void UnregisterName(string name) {
        columnFromName.Remove(name);

        if (NamesEqual(name, MakeName(defaultNameIndex - 1), true, table.Locale) != 0) {
            do {
                defaultNameIndex--;
            } while (defaultNameIndex > 1 &&
                     !Contains(MakeName(defaultNameIndex - 1)));
        }
    }

错误在第一行:没有检查是否未能在字典中找到该列。Remove

真正应该发生的事情是,字典应该使用不区分大小写的比较器进行初始化。columnFromNameStringComparer.OrdinalIgnoreCase