Delphi - 按其值访问类字符串属性

时间:2014-07-29 14:07:59

标签: delphi delphi-xe rtti

我有一个定义的类,它只包含字符串作为属性,我需要根据其值获取属性名称,如下例所示。在示例中只有3个属性,在真实生活类中几乎有1000个。问题是这个类被大量使用,我想知道我是否可以通过更快的方式获取属性名称。

unit Unit5;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs,RTTI, StdCtrls, Diagnostics;

type
  TConstDBElem = class
  public
    CCFN_1 : String;
    CCFN_2 : String;
    CCFN_3 : String;
    constructor Create;
  end;

  TForm5 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  end;

var
  Form5: TForm5;
  Obj: TConstDBElem;

implementation


{$R *.dfm}

procedure TForm5.Button1Click(Sender: TObject);
var iPos:Integer;
    timer:TStopwatch;
    Function GetName(const DBElemInstance : TConstDBElem; valueName: string) : string;
  var
    vrttiContext: TRttiContext;
    vrttiField : TRttiField;
    vType : TRttiType;
  begin
      vType := vrttiContext.GetType(TConstDBElem);

      for vrttiField in vType.GetFields do
        if (vrttiField.GetValue(DBElemInstance).ToString = valueName) then
        begin
           result := vrttiField.Name;
         end;
  end;

begin
  timer := TStopwatch.Create;
  timer.Start;
  Memo1.Lines.Clear;
  for iPos := 0 to 100000 do
    GetName(Obj,'TEST3');
  timer.Stop;
  Memo1.Lines.Add(FloatToStr(timer.Elapsed.TotalSeconds));
end;

constructor TConstDBElem.Create;
begin
  CCFN_1 := 'TEST1';
  CCFN_2 := 'TEST2';
  CCFN_3 := 'TEST3' ;
end;

initialization
  Obj := TConstDBElem.Create;

finalization
  Obj.Free;


end.

是的,我知道这是一个非常糟糕的设计,不应该这样做。有没有选择让这个搜索更快?

3 个答案:

答案 0 :(得分:3)

GetName()找到匹配项时,它不会停止循环,因此它会继续搜索更多匹配项。分配功能Result不会退出该功能,就像您清楚地认为的那样。因此,GetName()最终会返回最后匹配,而不是第一次匹配。当循环找到第一个匹配时,循环应调用Exit

Function GetName(const DBElemInstance : TConstDBElem; valueName: string) : string;
var
  vrttiContext: TRttiContext;
  vrttiField : TRttiField;
  vType : TRttiType;
begin
  vType := vrttiContext.GetType(TConstDBElem);

  for vrttiField in vType.GetFields do
    if (vrttiField.GetValue(DBElemInstance).ToString = valueName) then
    begin
      result := vrttiField.Name;
      Exit; // <-- add this
    end;
end;

或者,使用带有参数的Exit()版本:

Function GetName(const DBElemInstance : TConstDBElem; valueName: string) : string;
var
  vrttiContext: TRttiContext;
  vrttiField : TRttiField;
  vType : TRttiType;
begin
  vType := vrttiContext.GetType(TConstDBElem);

  for vrttiField in vType.GetFields do
    if (vrttiField.GetValue(DBElemInstance).ToString = valueName) then
    begin
      Exit(vrttiField.Name); // <-- assigns Result and exits at the same time
    end;
end;

在您的简单示例中,浪费时间搜索3个字段几乎不可察觉,但是当搜索1000个字段时,它会产生影响。

答案 1 :(得分:1)

您在评论中声明值在运行时永远不会更改。在这种情况下,您可以在启动时简单地构建一个单词字典,其中属性值为字典键,属性名称为字典值。

我假设该类的所有实例都具有相同的属性值。如果情况并非如此,那么每个实例需要一个字典。

答案 2 :(得分:0)

这是一个“糟糕的设计”,因为有人写了一个他们像C风格的结构一样对待的类。正如已经说过的那样,在类中没有定义任何属性,只有一堆公共数据成员,也就是“字段”。

没有封装,所以你改变结构所做的任何事情都会对任何使用它的单位产生深远的影响。我同意用TStringList或TDictionary替换IMPLEMENTATION会很聪明,但是......没有任何接口可以遵守!您有1000多个对公共数据成员的硬连线引用。

(我最后一次看到这样的东西是由一群VB程序员编写的代码,他们编写类似于包含公共数据成员的C风格结构,然后他们编写外部函数来访问数据,正如你在C.中所做的那样。只有他们将业务逻辑隐藏在访问器方法中,这会导致代码的随机部分直接引用类的数据成员。)

副手,我会说你完全误用了RTTI代码。当然,上面的优化会提高性能,但那又如何呢?这是错误的解决方案!

如果你真的想重构这个(你当然应该!),首先我要看看这个穷人阶级的使用是多么广泛,将公众变为私人,看看你得到了多少错误。

然后我从TStringList派生它,删除所有本地字段,并在类中移动GetName函数:

type
  TConstDBElem = class( TStringList )
  public
    constructor Create;
    function GetName( aName : string ) : string;
  end;

现在,如果我正确地解释你的例子,你想这样做来初始化对象:

constructor TConstDBElem.Create;
begin
  Add( 'TEST1=CCFN_1' );
  Add( 'TEST2=CCFN_2' );
  Add( 'TEST3=CCFN_3' );
end;

然后通过调用obj.GetName()来替换其他单元中的所有引用:

function TConstDBElem.GetName( aName : string ) : string;
begin
  Result := Values[aName];
end;

您正在用obj.CCFN_1替换对GetName(obj,'TEST1')(?)或obj.GetName('TEST1')的引用。

(也许我现在完全偏离基础了。抱歉,但我不知道你是如何在这个例子中使用这个类的,并且它对我来说并不是很有意义如果你说出你要映射的内容会更有意义。我的意思是......谁需要从与之相关的值中查找本地字段名称?一旦找到它,你会用它做什么? ?谁写了这个必须通过一些令人难以置信的扭曲来使代码工作,因为他/她确实在设计时不理解OOP!)

此时,您将成功地将此类(其他单元)的客户端与其内部实现分离,并将这些引用替换为对类中定义的接口(方法)的调用。

然后你可以做一些测试,看看如果改变实现会发生什么,比如从TStringList到TDictionary。但无论你如何分割它,我都无法想象TStringList或TDictionary会比你在你的例子中滥用RTTI系统的速度慢。 :)