Delphi XE4中CharInSet编译警告

6

我在 Delphi 7 的代码中有以下语句:

TMyCharSet = set of char;

当我将这段代码迁移到Delphi XE4时,在上面一行出现以下编译器警告。
W1050 WideChar reduced to byte char in set expressions.  Consider using 'CharInSet' function in 'SysUtils' unit.

我应该如何重新声明 TMyCharSet?


你可以改为使用 AnsiChar。Set 集合仅限于 256 个元素。例如,考虑如何解释 Const y : TMyCharSet=(['a','ش']); - bummi
2
你读过Marco的白皮书和官方文档吗?如果没有,请不要尝试进行Unicode迁移。 - David Heffernan
3个回答

7

一个集合无法包含大于一个字节的项目。由于UniCode Delphi中的Char是大小为两个字节的WideChar,因此集合类型是不适当的容器。

这里有一个基于记录的通用集合类型TSet<T>的示例。这意味着您不必考虑创建和销毁此类型的变量。将此类型用作简单类型的容器。我尝试模仿大部分集合类型的行为。可以使用+和-运算符添加和减去项目。还添加了in运算符。

注意:记录在动态数组中保存数据。将一个变量赋值给另一个变量将使两个变量使用相同的动态数组。内置Copy-On-Write(COW)保护将防止对一个变量的更改反映在另一个变量上。

unit GenericSet;

interface

Uses
  System.Generics.Defaults;

Type
  TSet<T> = record
    class operator Add(const aSet: TSet<T>; aValue: T) : TSet<T>; overload;
    class operator Add(const aSet: TSet<T>; const aSetOfT: TArray<T>) : TSet<T>; overload;
    class operator Add(const aSet1: TSet<T>; const aSet2: TSet<T>) : TSet<T>; overload;
    class operator Subtract(const aSet: TSet<T>; aValue: T): TSet<T>; overload;
    class operator Subtract(const aSet: TSet<T>; const aSetOfT: TArray<T>) : TSet<T>; overload;
    class operator Subtract(const aSet1: TSet<T>; const aSet2: TSet<T>) : TSet<T>; overload;
    class operator In(aValue: T; const aSet: TSet<T>): Boolean; overload;
    class operator In(const aSetOf: TArray<T>; const aSet: TSet<T>): Boolean; overload;
    class operator In(const aSet1: TSet<T>; const aSet2: TSet<T>): Boolean; overload;
  private
    FSetArray : TArray<T>;
    function GetEmpty: Boolean;
  public
    procedure Add(aValue: T);
    procedure AddSet(const setOfT: array of T); overload;
    procedure AddSet(const aSet: TSet<T>); overload;
    procedure Remove(aValue: T);
    procedure RemoveSet(const setOfT: array of T); overload;
    procedure RemoveSet(const aSet : TSet<T>); overload;
    function Contains(aValue: T): Boolean; overload;
    function Contains(const aSetOfT: array of T): Boolean; overload;
    function Contains(const aSet : TSet<T>): Boolean; overload;
    procedure Clear;
    property Empty: Boolean read GetEmpty;
  end;

implementation

procedure TSet<T>.Add(aValue: T);
begin
  if not Contains(aValue) then begin
    SetLength(FSetArray,Length(FSetArray)+1);
    FSetArray[Length(FSetArray)-1] := aValue;
  end;
end;

class operator TSet<T>.Add(const aSet: TSet<T>; aValue: T): TSet<T>;
begin
  Result.AddSet(aSet.FSetArray);
  Result.Add(aValue);
end;

class operator TSet<T>.Add(const aSet: TSet<T>; const aSetOfT: TArray<T>): TSet<T>;
begin
  Result.AddSet(aSet.FSetArray);
  Result.AddSet(aSetOfT);
end;

class operator TSet<T>.Add(const aSet1, aSet2: TSet<T>): TSet<T>;
begin
  Result.AddSet(aSet1.FSetArray);
  Result.AddSet(aSet2.FSetArray);
end;

procedure TSet<T>.AddSet(const setOfT: array of T);
var
  i : Integer;
begin
  for i := 0 to High(setOfT) do
    Self.Add(setOfT[i]);
end;

procedure TSet<T>.AddSet(const aSet: TSet<T>);
begin
  AddSet(aSet.FSetArray);
end;

procedure TSet<T>.RemoveSet(const setOfT: array of T);
var
  i : Integer;
begin
  for i := 0 to High(setOfT) do
    Self.Remove(setOfT[i]);
end;

procedure TSet<T>.RemoveSet(const aSet: TSet<T>);
begin
  RemoveSet(aSet.FSetArray);
end;

class operator TSet<T>.Subtract(const aSet1, aSet2: TSet<T>): TSet<T>;
begin
  Result.AddSet(aSet1.FSetArray);
  Result.RemoveSet(aSet2.FSetArray);
end;

class operator TSet<T>.Subtract(const aSet: TSet<T>;
  const aSetOfT: TArray<T>): TSet<T>;
begin
  Result.AddSet(aSet.FSetArray);
  Result.RemoveSet(aSetOfT);
end;

class operator TSet<T>.Subtract(const aSet: TSet<T>; aValue: T): TSet<T>;
begin
  Result.AddSet(aSet.FSetArray);
  Result.RemoveSet(aValue);
end;

class operator TSet<T>.In(aValue: T; const aSet: TSet<T>): Boolean;
begin
  Result := aSet.Contains(aValue);
end;

class operator TSet<T>.In(const aSetOf: TArray<T>; const aSet: TSet<T>): Boolean;
begin
  Result := aSet.Contains(aSetOf);
end;

class operator TSet<T>.In(const aSet1: TSet<T>; const aSet2: TSet<T>): Boolean;
begin
  Result := aSet2.Contains(aSet1.FSetArray);
end;

function TSet<T>.Contains(aValue: T): Boolean;
var
  i : Integer;
  c : IEqualityComparer<T>;
begin
  c := TEqualityComparer<T>.Default;
  Result := false;
  for i := 0 to Length(FSetArray)-1 do
    if c.Equals(FSetArray[i],aValue) then
      Exit(True);
end;

function TSet<T>.GetEmpty: Boolean;
begin
  Result := (Length(FSetArray) = 0);
end;

procedure TSet<T>.Clear;
begin
  SetLength(FSetArray,0);
end;

function TSet<T>.Contains(const aSetOfT: array of T): Boolean;
var
  i : Integer;
begin
  Result := High(aSetOfT) >= 0;
  for i := 0 to High(aSetOfT) do
  begin
    Result := Contains(ASetOfT[i]);
    if not Result then
      Exit(false);
  end;
end;

function TSet<T>.Contains(const aSet: TSet<T>): Boolean;
begin
  Result := Contains(aSet.FSetArray);
end;

procedure TSet<T>.Remove(aValue: T);
var
  i : Integer;
  c : IEqualityComparer<T>;
begin
  c := TEqualityComparer<T>.Default;
  for i := 0 to Length(FSetArray)-1 do
  begin
    if c.Equals(FSetArray[i],aValue) then
    begin
      SetLength(FSetArray,Length(FSetArray)); // Ensure unique dyn array
      if (i < Length(FSetArray)-1) then
        FSetArray[i] := FSetArray[Length(FSetArray)-1]; // Move last element
      SetLength(FSetArray,Length(FSetArray)-1);
      Break;
    end;
  end;
end;

end.

一个测试程序示例:
program ProjectGenericSet;
{$APPTYPE CONSOLE}    
uses
  GenericSet in 'GenericSet.pas';

var
 mySet,mySet1 : TSet<Char>;
begin
  mySet.AddSet(['A','B','C']);
  WriteLn(mySet.Contains('C'));
  WriteLn(mySet.Contains('D'));  // False
  mySet := mySet + 'D';
  WriteLn(mySet.Contains('D'));
  WriteLn('D' in mySet);
  mySet := mySet - 'D';
  WriteLn(mySet.Contains('D'));  // False
  mySet := mySet + TArray<Char>.Create('D','E');
  WriteLn(mySet.Contains('D'));
  WriteLn(mySet.Contains(['A','D']));
  mySet1 := mySet;
  // Testing COW
  mySet1.Remove('A');
  WriteLn(mySet.Contains('A'));
  mySet1:= mySet1 + mySet;
  WriteLn(mySet1.Contains('A'));
  mySet := mySet1;
  mySet1.Clear;
  WriteLn(mySet.Contains('A'));
  ReadLn;
end.

这看起来像是一个值类型,但却表现得像是一个引用类型。有时候。将一个变量赋值给另一个变量,它们都会引用同一个动态数组FSetArray。直到调用SetLength方法。 - David Heffernan
@DavidHeffernan,啊。好的,我会删除回答。 - LU RD
1
@DavidHeffernan,添加了COW保护以避免引用冲突。希望这次我做对了! - LU RD
+1 很好。我的原始评论并不是要完全忽略这个答案,只是一个评论而已。总之,COW可以胜任这项工作。 - David Heffernan

3
您会收到此警告是因为XE4在Char类型的变量中使用了WideChar(在String中使用了WideString),因此现在Char占用2个字节而不是1个字节。现在可以在String / Char中保留Unicode字符,但出于同样的原因,不再能够使用char集(在Delphi中,它是固定大小,32字节位图,最多可以保存256个项目)。
如果您仅使用#0..#127范围内的字符(仅拉丁/常规符号),则可以将Char替换为AnsiChar(但当您将其从Char分配时,您将看到另一个警告,您必须使用显式类型转换来抑制它)。
如果您需要国家/ Unicode符号,则在Delphi中没有“可用”的结构,但是您可以使用Tdictionary来实现此目的:
type
  TEmptyRecord = record end;

  TSet<T> = class(TDictionary<T,TEmptyRecord>)
  public
    procedure Add(Value: T); reintroduce; inline;
    procedure AddOrSetValue(Value: T); reintroduce; inline;
    function Contains(Value: T):Boolean; reintroduce; inline;
  end;

procedure TSet<T>.Add(Value: T);
var Dummy: TEmptyRecord;
begin
  inherited AddOrSetValue(Value, Dummy);
end;

procedure TSet<T>.AddOrSetValue(Value: T);
var Dummy: TEmptyRecord;
begin
  inherited AddOrSetValue(Value, Dummy);
end;

function TSet<T>.Contains(Value: T): Boolean;
begin
  result := inherited ContainsKey(Value);
end;

当然,您需要像任何其他常规类一样初始化它。 但它仍然非常高效(当然不像“set of”那么快,因为“set”始终受到256项最大尺寸的限制,但高度优化)。
或者,您可以创建自己的Unicode字符集类作为位图映射,它将占用8kb内存以保留所有位,并且几乎与“set of”一样快。

如果你使用聚合代替继承,那么你的类型会得到改进。 - David Heffernan
@david 它可以完全不覆盖使用,我只是让常用的方法更加友好。但在某些情况下,有其他继承方法可用是很有用的。因此,我们拥有原始结构的全部功能和最常用函数的友好界面。 - Andrei Galatyn
但是这样你得到的是一个字典而不是一个集合,这是不好的设计。继承是快速实现的方法,但在这种情况下最终得到的类型比较差。 - David Heffernan
你没有时间不做正确的事情。做错了,现在可能会节省时间,但长远来看会花费更多时间。技术债务。你也不需要很多时间来做正确的事情。你可以使用现有的集合。例如 Delphi Spring:https://code.google.com/p/delphi-spring-framework/source/browse/trunk/Source/Base/Collections/Spring.Collections.Sets.pas - David Heffernan
@David:世界并不是黑白分明的。对于所有情况,都没有通用的解决方案。在这种特定情况下,聚合并没有提供任何优势(代码甚至会慢一些)。我也不知道为什么我应该因为这样的设计而在未来花费额外的时间(除了非常普遍/学术性的考虑,这与现实生活中的这种特定情况无关)。 - Andrei Galatyn
显示剩余5条评论

-1

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