Delphi一个返回属性值的静态方法

时间:2014-06-10 08:42:46

标签: delphi properties static-methods

我正在制作Delphi VCL应用程序。有一个类TStudent,我有两个静态函数:一个从TStudent数组返回姓氏,另一个返回学生的第一个名字。他们的代码类似于:

class function TStudent.FirstNameOf(aLastName: string): string;
var i : integer;
begin
  for i := 0 to Length(studentsArray) - 1 do begin
    if studentsArray[i].LastName = aLastName then
    begin
       result := studentsArray[i].FirstName;
       Exit;
    end;
  end;
  result := 'no match was found';
end;

class function TStudent.LastNameOf(aFirstName: string): string;
var i : integer;
begin
  for i := 0 to Length(studentsArray) - 1 do begin
    if studentsArray[i].FirstName = aFirstName then
    begin
       result := studentsArray[i].LastName;
       Exit;
    end;
  end;
  result := 'no match was found';
end;

我的问题是如何避免两次编写几乎相同的代码。有没有办法将属性作为函数的参数传递。

5 个答案:

答案 0 :(得分:3)

您可以对此线性搜索使用带变量捕获的匿名方法。这种方法使您可以完全了解谓词。您可以测试任何类型的任何字段的相等性。您可以测试更复杂的谓词,例如或者检查。

代码可能如下所示:

class function TStudent.LinearSearch(const IsMatch: TPredicate<TStudent>; 
  out Index: Integer): Boolean;
var
  i: Integer;
begin
  for i := low(studentsArray) to high(studentsArray) do 
  begin
    if IsMatch(studentsArray[i]) then
    begin
      Index := i;
      Result := True;
      exit;
    end;
  end;

  Index := -1;
  Result := False;
end;

现在你需要做的就是提供一个合适的谓词。 TPredicate<T>单元中System.SysUtils的定义为:

type
  TPredicate<T> = reference to function (Arg1: T): Boolean;

所以你会像这样编写你的方法:

class function TStudent.GetFirstName(const LastName: string): string;
var 
  Index: Integer;
  IsMatch: TPredicate<TStudent>;
begin
  IsMatch := 
    function(Student: TStudent): Boolean
    begin
      Result := Student.LastName=LastName;
    end;

  if not LinearSearch(IsMatch, Index) then
  begin
    raise ...
  end;
  Result := studentsArray[Index].FirstName;
end;

同样适用于GetLastName

如果您的Delphi不支持匿名方法,那么您将无法使用变量捕获,并且必须使用of object方法类型找到更复杂的方法。但是,基本思路将大致相同。

答案 1 :(得分:3)

我没有测试过,但我相信这可能是一个解决方案。

uses TypInfo;

class function TStudent.GetProperty( propertyName: string, searchValue : Variant ) : Variant ;
var i : integer;
begin
  for i := 0 to Length(studentsArray) - 1 do begin
    if GetPropValue( studentsArray[i], propertyName ) = searchValue 
       result :=  GetPropValue( studentsArray[i], propertyName );
  end;
  // your code in case of not finding anything

end;

答案 2 :(得分:1)

如果您使用的是Delphi 2010或更高版本,则可以使用Extended RTTI:

uses
  ..., Rtti;

type
  TStudent = class
  public
    FirstName: String;
    LastName: String;

    class function GetNameOf(const aFieldToFind, aNameToFind, aFieldToReturn: string): string;
   end;

class function TStudent.GetNameOf(const aFieldToFind, aNameToFind, aFieldToReturn: string): string;
var
  i : integer;
  ctx: TRttiContent;
  StudentType: TRttiType;
  Field: TRttiField;
  Value: TValue;
begin
  ctx := TRttiContext.Create;
  StudentType := ctx.GetType(TStudent);
  Field := StudentType.GetField(aFieldToFind);

  for i := 0 to Length(studentsArray) - 1 do
  begin
    if Field.GetValue(@studentsArray[i]).AsString = aNameToFind then
    begin
      Result := StudentType.GetField(aFieldToReturn).GetValue(@studentsArray[i]).AsString;
      Exit;
    end;
  end;
  Result := 'no match was found';
end;

然后你可以这样称呼它:

FirstName := TStudent.GetNameOf('LastName', 'Smoe', 'FirstName');

LastName := TStudent.GetNameOf('FirstName', 'Joe', 'LastName');

答案 3 :(得分:1)

如果稍微重新构建TStudent记录,一切都会变得更容易。不是使用具有不同名称的多个字符串字段,而是声明具有枚举范围的字符串数组。

为枚举提供有意义的名称,并添加搜索功能,以指定搜索字段和结果字段。

Type   
  TStudentField = (sfFirstName,sfLastName);  // Helper enumeration type

  TStudent = record
    Field: array[TStudentField] of String;
    class function SearchNameOf(searchField: TStudentField; 
      const aSearchName: string; resultField: TStudentField): string; static;
  end;

这是一个测试示例:

program ProjectTest;

{$APPTYPE CONSOLE}

Type   
  TStudentField = (sfFirstName,sfLastName);

  TStudent = record
    Field: array[TStudentField] of String;
    class function SearchNameOf(searchField: TStudentField; const aSearchName: string; resultField: TStudentField): string; static;
  end;

var
  studentsArray : array of TStudent;

class function TStudent.SearchNameOf(searchField: TStudentField; const aSearchName: string; resultField: TStudentField): string;
var
  i : integer;
begin
  for i := 0 to Length(studentsArray) - 1 do begin
    if (studentsArray[i].Field[searchField] = aSearchName) then
    begin
      Result := studentsArray[i].Field[resultField];
      Exit;
    end;
  end;
  result := 'no match was found';
end;

begin
  SetLength(studentsArray,2);
  studentsArray[0].Field[sfFirstName] := 'Buzz';
  studentsArray[0].Field[sfLastName] := 'Aldrin';
  studentsArray[1].Field[sfFirstName] := 'Neil';
  studentsArray[1].Field[sfLastName] := 'Armstrong';
  WriteLn(TStudent.SearchNameOf(sfFirstName,'Neil',sfLastName));
  ReadLn;
end.

答案 4 :(得分:-3)

您可以使用单个getter函数支持的index说明符的多个属性,就像使用常规数组属性一样:

  TDefault = class(TObject)
  private
    class function GetProp(const FindWhat: string; FindWhere: Integer): string;
        static;
  protected
    /// <remarks>
    ///   You don't really need this one. I've added it for an illustration
    ///   purposes.
    /// </remarks>
    class property Prop[const FindWhat: string; FindWhere: Integer]: string read GetProp;
  public
    class property A[const FindWhat: string]: string index 0 read GetProp;
    class property B[const FindWhat: string]: string index 1 read GetProp;
  end;

{ ... }

class function TDefault.GetProp(const FindWhat: string; FindWhere: Integer): string;
begin
  case FindWhere of
    0: Result := 'Hallo!';
    1: Result := 'Hello!';
  end;
  Result := Result + ' ' + Format('searching for "%s"', [FindWhat]);
end;

如您所见,类属性与实例属性相同。

我必须说,在属性getter中执行搜索是一个非常糟糕的主意。