您的位置:首页 > 编程语言 > Delphi

Delphi 类引用 Class Reference 元类 MetaClass 用法

2014-10-12 11:02 330 查看

delphi中类引用的使用实例

类引用
类引用(Class Reference)是一种数据类型,有时又称为元类(MetaClass),是类的类型的引用。类引用的定义形式如下:

class of type


例如:

type
SomeClass = class of TObject;

var
AnyObj: SomeClass;


TObject = class
end;

TClass = class of TObject;


下面的例子说明了类引用的用法:

program Project1;
{$APPTYPE CONSOLE}

type
TPerson = class { 人员类 }
Name : string; { 姓名 }
end;

TEmployee = class( TPerson ) { 职员类 }
DeptName : string; { 部门名称 }
procedure Infor; { 显示职员信息 }
end;

CRef = class of TObject; { 定义了一个"类引用"数据类型 }

var
Employee : array [ 0 .. 1 ] of TObject; { 类的变量数组 }
i : Integer; { 循环变量 }
CR : array [ 0 .. 1 ] of CRef; { 类引用数组 }

{ TEmployee }
procedure TEmployee.Infor;
begin
Writeln( ’ 姓名 : ’, name, ’ ; 部门名称 : ’, DeptName );
end;

begin
CR[ 0 ] := TPerson; { 给类引用赋值 }
CR[ 1 ] := TEmployee;
for i := 0 to 1 do
begin
Employee[ i ] := CR[ i ].Create; { 创建对象 }
if Employee[ i ] is TEmployee then { 判断对象的类型 }
begin
( Employee[ i ] as TEmployee ).Name := ’ 残月 ’;
( Employee[ i ] as TEmployee ).DeptName := ’ 人事部 ’;
( Employee[ i ] as TEmployee ).Infor;
end;
end;
Readln;

end.


运行结果如下:

姓名:残月;部门名称:人事部

注意:上面定义了一个类引用类型的数组,其中的两个元素的数值分别为不同的两个类的类型。

代码示例:你是真的对Delphi很了解么?

procedure StepEditor( strgrid : TStringGrid; Step : TStep );
var
sValue, sField : string;
EditorClass : TStepEditorClass;
Editor : TStepEditor;
begin
sField := strgrid.Cells[ 0, strgrid.Selection.Top ];
sValue := strgrid.Cells[ 1, strgrid.Selection.Top ];
EditorClass := EditorClassList.Editors[ sField ];
Editor := EditorClass.Create;
Editor.Field := sField;
Editor.Step := Step;
Editor.Edit( sValue );
Editor.Free;
strgrid.Cells[ 1, strgrid.Selection.Top ] := sValue;
end;


EditorClass 是一个Class of Class, 也就是类的类 比如 TFormClass = Class of TForm;

但是不同于:TFormClass = Class( TForm ); 这是两个概念!

而 EditorClassList 里面存放的就是 类的类的列表;

Editor := EditorClass.Create;

Create是类方法,而不是对象方法,所以可以由 EditorClass来创建EditorClass的一个实例

补充:

TStepEditor = Class( TObject )
...
End;

TStepEditorClass = Class of TStepEditor;

基本概念

元类(meta class),也叫类引用类型(class-reference type),

可以看成是一种类的类型,以该类型声明的变量的值代表一个类。比如:

type

TClass = Class of TObject;

这样就声明了一个元类的类型。然后可以有这样的变量声明:

Var
AClass: TClass;

那么,就可以有这样的用法:

AClass := TObject;

或者:
AClass := TButton;

或者:
AClass := TForm;

等等。
因为TClass是一个TObject类型的元类,而TButton,TForm等都是自TObject派生而来,

因而TButton和TForm这样的值对于AClass都是可接受的。

A class-reference type, sometimes called a metaclass, is denoted by a construction of the form

class of type

where type is any class type.

The identifier type itself denotes a value whose type isclass of type.

If type1 is an ancestor of type2, thenclass of type2 is assignment-compatible withclass of type1. Thus

type TClass = class of TObject;
var
AnyObj: TClass;


declares a variable called AnyObj that can hold a reference to any class.

(The definition of a class-reference type cannot occur directly in a variable declaration or parameter list.)

You can assign the value nil to a variable of any class-reference type.

To see how class-reference types are used, look at the declaration of the constructor for TCollection (in the Classes unit):

type TCollectionItemClass = class of TCollectionItem;
...
constructor Create(ItemClass: TCollectionItemClass);


This declaration says that to create a TCollection instance object,

you must pass to the constructor the name of a class descending from TCollectionItem.

Class-reference types are useful when you want to invoke a class method

or virtual constructor on a class or object whose actual type is unknown at compile Time .

元类 就是类之类, 如果说 对象(引用) 是类的变量,那么 元类变量 就是 类的 类型的 变量.
TForm = class(TCustomForm)
TFormClass= class of TForm

{ 定义部分 }
interface

type
TMyClass = class( TObject )
end;

TMyClassClass = class of TMyClass;

TMyClass1 = class( TMyClass )
end;

TMyClass2 = class( TMyClass )
end;

{ 执行部分 }
implementation

procedure MyProcedure;
var
MyClass : TMyClassClass;
MyObj : TMyClass;
begin
{ 创建TMyClass1的实例 }
MyClass := TMyClass1;
MyObj := MyClass.Create;
MyObj.Free;

{ 创建TMyClass2的实例 }
MyClass := TMyClass2;
MyObj := MyClass.Create;
MyObj.Free;
end;


http://blog.csdn.net/blue_morning/article/details/8815609

这个概念本来在一个关于Delphi RTTI 介绍的文档中已经说得很清楚了。

但没有任何关于实际使用的介绍,在我明白了这个概念和如何使用后决定写一个使用说明以方便大家使用。

类的类在什么时候使用:

知道父类但需要创建具体的子类时(你不知道子类会是什么)

例如:

一个Delphi Exe程序中项目文件的Application.CreateForm,跟踪下源代码就能明白,

Delphi实现了在根本不知道我们会从TForm派生出什么类的情况下,实现了对这个类的创建。

 TComponentClass = class of TComponent;

procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
begin
Instance := TComponent(InstanceClass.NewInstance);
Instance.Create(Self);
...
end;


关键的代码就是加粗的这两句和类的类声明

本质:

类的类在声明时,说明相应的类及子类会被编译器附加额外的信息(RTTI),

以让系统可以找到具体子类的Create和NewInstance地址。应该就是这样。

代价:

额外的RTTI信息会使我们的类占用额外的内存,这是便利的代价。

简单的问题复杂的说明

本来问题已经说明,但还是存在一个问题:

我们的代码中什么地方需要使用class of ?

我发现这个问题说明起来很复杂,我举个我人个开发使用的例子。

在做数据库程序开发时:

我先定义一个TTableSet对象,其功能类似DataModule。

用于放置TExportTable,TExportTable类其功能类似TDataSet。

我定义了它的增、删、改、查等基本操作。

TTableSet对象有一个Add方法,大概代码如下:

procedure TTableSet.Add(const AExoprtObjectInfo: record)
var
ExprotTable: TExportTable;
begin
ExprotTable := TExportTable.Create(nil)
{ 根据AExoprtObjectInfo的数据内容具体化ExportTable对象以方便复用代码 }
end;


然后,在具体的业务功能(例如入库单管理)中需要从TExportTable继承一个入库单类

TInStorageBill = class(TExportTable)
{ 一些具体的类属性和方法 }
{ 覆盖TExportTable的Create方法以创建相应的资源 }
end;


废话了那么多,问题才终于出现了:“我怎么才能在TTableSet.Add()方法中创建TInStorageBill对象?”

或换而言之:“我怎么在在知道父类的情况下创建其不确定的子类?”。 而你们都知道答案了。

http://bbs.csdn.net/topics/90321

关于物件参考(Object reference)与类别参考(Class reference):

type
TMyClass = class of TForm; { 宣告了关于TForm的一个类别参考,意即C++中的别名。}

var
MyClass: TForm; { 宣告了关于TForm的一个物件参考。}


注意点:
建构函数呼叫物件参考时将初始化一个物件的所有栏位,并返回一个指向此物件的指针。

建构函数呼叫类别参考时并不初始化任何栏位,只简单的返回一个指向此物件的指针。

Delphi Class of 类引用也就是类的类型,也可说是指向类的指针

Type
TControlCls = Class of TControl;

function CreateComponent(ControlCls: TControlCls): TControl;
begin
result:=ControlCls.Create(Form1);
...
end;

function CreateComponent(ControlCls: TControl): TControl;
begin
result:=ControlCls.Create(Form1);
...
end;


前者要求传入一个 类, 而后者要求传入一个 对象(类的实例)

type
MyClassRef = calss of TMyClass; { 表示 MyClassRef 为指向 TMyClass 或 其父类 的指针 }


类的引用 就像指向 类的指针 一样

类的引用 就是 类 的类型,可以声明一个类的引用变量, 赋给它一个类,可以通过这个 类的引用变量 创建 对象 的实例。

类之类

当你不确定调用的类模型时候用到类之类。也可以说是类指针~

How can I create an Delphi object from a class reference and ensure constructor execution?

How can I create an instance of an object using a class reference,

and ensure that the constructor is executed?

In this code example, the constructor of TMyClass will not be called:

type
TMyClass = class(TObject)
MyStrings: TStrings;
constructor Create; virtual;
end;

constructor TMyClass.Create;
begin
MyStrings := TStringList.Create;
end;

procedure Test;
var
Clazz: TClass;
Instance: TObject;
begin
Clazz := TMyClass;
Instance := Clazz.Create;
end;


Your code slightly modified:

type
TMyObject = class(TObject)
MyStrings: TStrings;
constructor Create; virtual;
end;

TMyClass = class of TMyObject;

constructor TMyObject.Create;
begin
inherited Create;
MyStrings := TStringList.Create;
end;

procedure Test;
var
C: TMyClass;
Instance: TObject;
begin
C := TMyObject;
Instance := C.Create;
end;


Use this:

type
TMyClass = class(TObject)
MyStrings: TStrings;
constructor Create; virtual;
end;

TMyClassRef = class of TMyClass;

constructor TMyClass.Create;
begin
MyStrings := TStringList.Create;
end;

procedure Test;
var
MyClassRef: TMyClassRef;
Instance: TObject;
begin
MyClassRef := TMyClass; { you can use TMyClass or any of its child classes. }
Instance := MyClassRef.Create; { virtual constructor will be used }
end;


Alternatively, you can use a type-casts to TMyClass (instead of class of TMyClass).

Alexander's solution is a fine one but does not suffice in certain situations.

Suppose you wish to set up a TClassFactory class where TClass references

can be stored during runtime and an arbitrary number of instances retrieved later on.

Such a class factory would never know anything about the actual types of the classes it holds

and thus cannot cast them into their according meta classes.

To invoke the correct constructors in such cases, the following approach will work.

First, we need a simple demo class (don't mind the public fields, it's just for demonstration purposes).

interface

uses
RTTI;

type
THuman = class(TObject)
public
Name: string;
Age: Integer;

constructor Create(); virtual;
end;

implementation

constructor THuman.Create();
begin
Name:= 'John Doe';
Age:= -1;
end;


Now we instantiate an object of type THuman purely by RTTI and with the correct constructor call.

procedure CreateInstance();
var
someclass: TClass;
c: TRttiContext;
t: TRttiType;
v: TValue;
human1, human2, human3: THuman;
begin
someclass:= THuman;

{ Invoke RTTI }
c:= TRttiContext.Create;
t:= c.GetType(someclass);

{ Variant 1a - instantiates a THuman object but calls constructor of TObject }
human1:= t.AsInstance.MetaclassType.Create;

{ Variant 1b - same result as 1a }
human2:= THuman(someclass.Create);

{ Variant 2 - works fine }
v:= t.GetMethod('Create').Invoke(t.AsInstance.MetaclassType,[]);
human3:= THuman(v.AsObject);

{ free RttiContext record (see text below) and the rest }
c.Free;

human1.Destroy;
human2.Destroy;
human3.Destroy;
end;


You will find that the objects "human1" and "human2" have been initialized to zero,

i.e., Name='' and Age=0, which is not what we want.

The object human3 instead holds the default values provided in the constructor of THuman.

Note, however, that this method requires your classes to have constructor methods with not parameters.

All the above was not conceived by me but explained brillantly and in much more detail (e.g., the c.Free part)

in Rob Love's Tech Corner.

vcv
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: