DELPHI盒子
!实时搜索: 盒子论坛 | 注册用户 | 修改信息 | 退出
检举帖 | 全文检索 | 关闭广告 | 捐赠
技术论坛
 用户名
 密  码
自动登陆(30天有效)
忘了密码
≡技术区≡
DELPHI技术
移动应用开发
Web应用开发
数据库专区
报表专区
网络通讯
开源项目
论坛精华贴
≡发布区≡
发布代码
发布控件
文档资料
经典工具
≡事务区≡
网站意见
盒子之家
招聘应聘
信息交换
论坛信息
最新加入: sql2003s
今日帖子: 0
在线用户: 1
导航: 论坛 -> DELPHI技术 斑竹:liumazi,sephil  
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2022/9/26 0:39:06
标题:
奇怪的问题:关于泛型参数约束的... 浏览:803
加入我的收藏
楼主: 晚上在测试TFrameStand的Demo的时候,突然有个Demo编译错误了,原来在10.3.3的时候是好好的,Demo里的Dialog:

procedure TMainForm.FormCreate(Sender: TObject);
begin
  // this action will be tied to the rectangle implementing
  // the "faded" background of the stand (see 'background_cancel')
  // element in StyleBook1 and also tied to Button_cancel TButton
  // on the frame

  FrameStand1.CommonActions.Add('*_cancel',
      procedure(AInfo: TFrameInfo<TFrame>)
      begin
        if (AInfo.Frame is TColorDialogFrame) then
          TColorDialogFrame(AInfo.Frame).Cancel;
      end
  );
end;
这里提示错误:
 [dcc32 Error] uMain.pas(88):
  E2010 Incompatible types: 'System.SysUtils.TProc<uMain.TMyBase>' and 'Procedure'

要改成这样:
  FrameStand1.CommonActions.Add('*_cancel',
      procedure(AInfo: TSubjectInfo)
      begin
        if (AInfo is TFrameInfo<TFrame>) and (TFrameInfo<TFrame>(AInfo).Frame is TColorDialogFrame) then
          TColorDialogFrame(TFrameInfo<TFrame>(AInfo).Frame).Cancel;
      end
  );

定义是:TFrameInfo<T: TFrame> = class(TSubjectInfo)

这不是开玩笑吗? 子类当参数不行了?
11.1的版本没有试,没装这个控件.......
----------------------------------------------
-
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2022/9/26 0:43:01
1楼: 哎,果然,自己写了个简单的,还是错误。。。

unit uMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs;

type
  //TMyProc<T> = reference to procedure (Arg1: T);

  TMyBase = class

  end;

  TMyTestObj = class(TMyBase)
  private
    FSS: string;
    procedure SetSS(const Value: string);
  public
    property SS: string read FSS write SetSS;
  end;

  TMyAction<T: TMyBase> = class

  public
    procedure Add(a: string; AProc: TProc<T>);
  end;

  TForm1 = class(TForm)
  private
    { Private declarations }
  public
    procedure DoAction;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TMyObj<T> }

procedure TMyAction<T>.Add(a: string; AProc: TProc<T>);
begin
  //........
end;

{ TForm1 }

procedure TForm1.DoAction;
var
  L: TMyAction<TMyBase>;
begin
  L := TMyAction<TMyBase>.Create;
  // 10.4.2下, 注释的语句编译错误:  (10.3.3 是可以正确编译的!!!
  // [dcc32 Error] uMain.pas(88):
  //  E2010 Incompatible types: 'System.SysUtils.TProc<uMain.TMyBase>' and 'Procedure'
//  L.Add('aaa',
//    procedure(K: TMyTestObj)
//    begin
//      K.SS := 'ddd';
//    end);

  //10.4.2 要这样处理: 用基类做参数就不会错....
  L.Add('aaa',
    procedure(K: TMyBase)
    begin
      if K is TMyTestObj then
      begin
        TMyTestObj(K).SS := 'ddd';
      end;
    end);
end;

{ TMyTestObj }

procedure TMyTestObj.SetSS(const Value: string);
begin
  FSS := Value;
end;

end.
----------------------------------------------
-
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2022/9/26 1:10:50
2楼: Demo的编译错误信息是:
[dcc32 Error] Forms.Main.pas(100): E2010 Incompatible types: 'System.SysUtils.TProc<SubjectStand.TSubjectInfo>' and 'Procedure'

搞混了。。。
----------------------------------------------
-
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2022/9/26 2:11:12
3楼: 这个好像是编译器规则改了?作者也建议修改代码例子:
https://github.com/andrea-magni/TFrameStand/issues/63
----------------------------------------------
-
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2022/9/26 2:59:00
4楼: 楼上,你的问题我前些天遇到过。后来发现又能通过了。忘记是什么原因了。当时没写记录。
----------------------------------------------
-
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2022/9/26 3:07:33
5楼: 查了一下我当时写的简单的测试代码,我自己写的,在 D10.4.2 底下是能够运行的。

class function TFrameBase.NewFrame<T>: T;
begin
  Result := T.Create(nil);
  FList.Add(Result);
end;

procedure TFrame2.Button2Click(Sender: TObject);
var
  AFrame: TFrameBase;
begin
  AFrame := Self.NewFrame<TFrame3>;
  AFrame.Show;
end;

上述代码编译是能够通过的。

但你提到的问题,我确实也遇到过。当时也搞清楚出问题的原因了,现在不记得了。人老了记性太差,不写记录就忘记。
----------------------------------------------
-
作者:
男 emailx45 (emailx45) ▲▲▲▲△ -
注册会员
2022/9/26 7:08:06
6楼: 许多人可能已经知道,Object Pascal 很大程度上基于对象的类型,更准确地说,是基于对象类型的“名称”!

这样,在翻译信息时,代码中引用的对象的类型(类型的名称)之间存在冲突,并且编译器必须在这一点上尝试进行推断,并且,在最后一种情况,它可能会在识别相同的不确定时更容易引发错误。

这样,许多错误消息实际上并不是真正的消息,而是许多消息中最有可能的消息。

这样一来,除了程序员在期望有祖先类的地方使用继承类(子类)时出现错误,最终也会造成混乱。

因此,正如游戏中所说:刺客信条:
一切都被允许......

然而,并非一切都是可取的。
----------
As many of you may already know, Object Pascal is heavily based on the object's type, more precisely, on the "name" of the object's type!

In this way, when translating the information, there is a conflict between the types (the names of the types) of the objects being referenced in the code, and, the compiler must be trying to make an inference at this point, and, in the last case, it it may throw an error more likely when it is not known for sure in the identification of the same.

In this way, many error messages are not, in fact, the real message, but the most likely among many messages.

In this way, in addition to the programmer's mistake when using an inherited class (sub-class) in the place where an ancestor class is expected, it can also contribute to the confusion in the end.

Thus, as it is said in the game: Assassins Creed:
Everything is allowed....

However, not everything is desirable.


RAD Studio 11.2 Alexandria 32bits

try this:


unit Unit1;

type
  TMyBase = class

  end;

  TMyTestObj = class(TMyBase)
  private
    FSS: string;
    procedure SetSS(const Value: string);
  public
    property SS: string read FSS write SetSS;
  end;

  TMyAction<T: class, constructor> = class // to generic usage...
    // TMyAction<T: TMyBase> = class       // TMyBase usage...

  public
    procedure Add(a: string; AProc: TProc<T>);
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    procedure DoAction;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
{ TMyTestObj }

procedure TMyTestObj.SetSS(const Value: string);
begin
  FSS := Value;
end;

{ TMyAction<T> }

procedure TMyAction<T>.Add(a: string; AProc: TProc<T>);
var
  MyInstanceX: T;
begin
  if Assigned(AProc) then
    begin
      // MyInstanceX := T(TMyBase.Create);  // to TMyBase usage...
      // MyInstanceX := T(TMyTestObj.Create); // to TMyTestObj usage...
      MyInstanceX := T.Create; // to "T" generic usage...
      try
        AProc(MyInstanceX);
        //
        if (TObject(MyInstanceX).InheritsFrom(TMyTestObj)) then
          ShowMessage(TMyTestObj(MyInstanceX).FSS);
      finally
        if Assigned(MyInstanceX) then
          MyInstanceX.Free;
      end;
    end;
end;

{ TForm1 }

procedure TForm1.DoAction;
var
  // L: TMyAction<TMyBase>;
  L: TMyAction<TMyTestObj>;
begin
  L :=nil;  // this works too!
  //
  // L := TMyAction<TMyBase>.Create;  // uncomment this line
  // or...
  // L := TMyAction<TMyTestObj>.Create; // or uncomment this line
  //
  try
    //
    // 10.4.2下, 注释的语句编译错误:  (10.3.3 是可以正确编译的!!!
    // [dcc32 Error] uMain.pas(88):
    // E2010 Incompatible types: 'System.SysUtils.TProc<uMain.TMyBase>' and 'Procedure'

 L.Add('aaa',
    procedure(K: TMyTestObj)
    begin
       K.SS := 'ddd';    // <----- OK NOW for TMyTestObj
    end);


    // 10.4.2 要这样处理: 用基类做参数就不会错....
    L.Add('aaa',
      // procedure(K: TMyBase )
      // or...
      procedure(K: TMyTestObj)
      begin
        if K.InheritsFrom(TMyTestObj) then
          ShowMessage('K.InheritsFrom(TMyTestObj) ')
        else if K.InheritsFrom(TMyBase) then
          ShowMessage('K.InheritsFrom(TMyBase) ');
        //
        if K is TMyTestObj then
          begin
          TMyTestObj(K).SS := 'now: ' + DateTimeToStr(now);
          end
        else
          ShowMessage(K.ClassName + ', K is NOT TMyTestObj ');

      end);
  finally
   if Assigned(L) then
      L.Free;
  end;
end;

initialization

ReportMemoryLeaksOnShutdown := true;

finalization

end.
此帖子包含附件:
PNG 图像
大小:91.3K
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!
作者:
男 emailx45 (emailx45) ▲▲▲▲△ -
注册会员
2022/9/26 7:19:34
7楼: Name type:


type
  TAAA = TMyAction<TMyBase>;
  TBBB = TMyAction<TMyTestObj>;

procedure TForm1.Button2Click(Sender: TObject);
begin
  if TAAA = TBBB then;
  // [dcc32 Error] uView.MainForm.pas(101): E2010 Incompatible types: 'class of uView.MainForm.TMyAction<uView.MainForm.TMyBase>' and
  //.......... 'class of uView.MainForm.TMyAction<uView.MainForm.TMyTestObj>'
end;
----------------------------------------------
The higher the degree, the greater the respect given to the humblest!
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2022/9/26 9:37:42
8楼: 如果是<T:class>,那就可以。
如果是<T:TMyBase>那就规定了此类参数必须是TMyBase,它的子类都不行。(要用TMyBase的子类,只能是在代码里向下转型)。
这样问题来了,为什么要用<T:TMyBase>就是因为要约束啊。
哪位电脑上有10.3.3以前版本的,试试,没有这个问题的。。。
----------------------------------------------
-
作者:
男 hs_kill (lzl_17948876) ★☆☆☆☆ -
普通会员
2022/9/26 12:15:28
9楼: 我到是觉得这样没什么问题, 就以2楼的例子来说
  L := TMyAction<TMyBase>.Create;
  L.Add('aaa',
    procedure(K: TMyTestObj)
    begin
      K.SS := 'ddd';
    end);
本来就是要读TMyTestObj.ss属性, 那么为什么不直接定义TMyTestObj的泛型, 而非要定义一个父类
换句话说, 如果传过来的是TMyBase的另一个子类, 没有ss这个属性怎么办

正确的做法既然定义父类泛型,又需要读取子类属性, 那就应该在实现里去判断是否子类
如果确定是读取一个固定的子类属性, 那么泛型定义就要明确是这个子类才对
----------------------------------------------
http://www.cnblogs.com/lzl_17948876/
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2022/9/26 12:32:34
10楼: 当然写简单的例子,用TMyBase是多此一举。
但是写控件,写框架,复杂一点的代码就不一样了,因为还不知道子类是啥,怎么直接用子类?这样的例子太多了....
----------------------------------------------
-
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2022/9/26 12:38:56
11楼: 就比如一个普通过程
procedure DoIt(AObj: TMyBase);
begin

end;

调用的时候,
var
  LObj: TMyTestObj;

DoIt(LObj);  //这一句编译通不过,是什么感觉。。。。。
----------------------------------------------
-
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2022/9/26 13:40:49
12楼: 楼主可能根本没看我的回帖。

基类里面的泛型是基类,到了子类,调用基类的方法时,是可以传入子类的类名称的。我在 D10.4 上测试通过了。代码也贴在上面了。
----------------------------------------------
-
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2022/9/26 13:48:37
13楼: 之前,我学习 SkiaForDelphi 那套控件的 Demo,发现它的界面框架有点意思,分析了一下,也写了一篇博客。

它这个框架也是用了泛型,在基类里面用泛型,调用时传入子类的类名称。

它的代码我也是在 D10.4.2 底下编译运行的,没有问题。

博客文章地址如下:

https://blog.csdn.net/pcplayer/article/details/126592420
----------------------------------------------
-
作者:
男 hs_kill (lzl_17948876) ★☆☆☆☆ -
普通会员
2022/9/26 14:30:20
14楼:
当然写简单的例子,用TMyBase是多此一举。
但是写控件,写框架,复杂一点的代码就不一样了,因为还不知道子类是啥,怎么直接用子类?这样的例子太多了....



既然不知道子类是什么, 那么就不应该在代码里调用子类特有属性或函数
既然不调用子类特有的数据, 那么调用代码也就无需明确子类类型了
----------------------------------------------
http://www.cnblogs.com/lzl_17948876/
作者:
男 wk_knife (wk_knife) ★☆☆☆☆ -
盒子活跃会员
2022/9/26 15:00:02
12楼: 换成接口,IMyBase,都走一个接口
----------------------------------------------
-
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2022/9/26 15:06:01
15楼: 嗯,传入子类的名称肯定是可以,我的代码里也说了,可以向下转型。
我的意思是说,为什么原型参数不能用子类???
因为在10.3.3的时候肯定是可以的,我在改造FMX TGrid的banding的时候,就碰到这样的问题了,原来在10.3.3可以的代码,在10.4以后就编译错误,就是这个泛型参数的原型问题。
正好是用了大佬的TFrameStand控件,也碰到了这个问题,所以想问问编译器这样的改变是为什么?
delphi的帮助里关于泛型约束的没有更,还和原来一样的。
----------------------------------------------
-
作者:
男 wk_knife (wk_knife) ★☆☆☆☆ -
盒子活跃会员
2022/9/26 15:09:22
15楼: 另外用这样定义好像也能解决问题,还更方便
TMyAction = class

  public
    procedure Add<T>(a: string; AProc: TProc<T>);
  end;
----------------------------------------------
-
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2022/9/26 15:12:40
16楼: @hs_kill (lzl_17948876)
不是子类特有的,是父类的public属性或特性。。。

有许多这样的代码,可以看看FMX Grid的Banding源码,就有这个问题,10.4版本后,FMX Grid的Banding源码改了好多地方,在10.3.3的时候的代码,到10.4以后就不行了
----------------------------------------------
-
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2022/9/26 15:17:32
17楼: 大侠们,不是我我我写的代码。。。。。。。
我写的只是个简单的例子,你们github个TFrameStand的代码吧,然后编译试下。。。
有条件的可以在10.3.3和10.4以后的版本分别测试,就有中问题。
----------------------------------------------
-
作者:
男 roadrunner (roadrunner) ★☆☆☆☆ -
盒子活跃会员
2022/9/27 1:08:13
18楼: TProc<TMyBase>不是一个类
所以,它和TProc<TMyTestObj>是不兼容的

如果老版本能编译过去,那就是老版本的BUG,编译过不了才是合理的
----------------------------------------------
-
作者:
男 keymark (嬲) ▲▲△△△ -
注册会员
2022/9/27 11:25:21
19楼: 不记得是那个版本开始
MyTestObj1 MyTestObj2 实现的代码接口一样 只是两个class
TMyTestObjA = MyTestObj1
TMyTestObjB = MyTestObj2

LObj1: TMyTestObjA;
LObj2: TMyTestObjB;

LObj1=LObj2;
这样是不被允许= 的 检测增强了。
典型
    老代码 indy 那个array byte
声明个和他一样的不行 得用他声明的。。。。。。
----------------------------------------------
[alias]  co = clone --recurse-submodules  up = submodule update --init --recursiveupd = pullinfo = statusrest = reset --hard懒鬼提速http://qalculate.github.io/downloads.htmlhttps://www.cctry.com/
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2022/9/27 14:59:08
20楼: 翻了翻Marco cantu的书,<T:class>这个叫一般约束,<T:TControl>这个叫特定类约束,他也没提到这个问题,只是提醒说<T:TControl>并不能提供给我们想象的那样的好处,如果想用继承来得到好处,还不如直接使用经典的继承方法来实现。并建议我们使用接口约束<T: IControl>。我看了几个版本的书,关于这一部分的表述,都一样,10.4版本前的书和以后的书都一样,照抄的。
我觉得老的规则更方便,但是和通用类型检测规则是不一致的。
许多第三方甚至DELPHI自己的源码里都存在这个问题,我们运用的时候得自己处理(明确的向下转型处理)。
----------------------------------------------
-
作者:
男 roadrunner (roadrunner) ★☆☆☆☆ -
盒子活跃会员
2022/9/27 19:36:06
21楼: 楼上你还没理解,和什么约束没有关系
TProc<TMyBase> 等价于 procedure(a : TMyBase);
TProc<TMyTestObj> 等价于 procedure(a : TMyTestObj);
它们分别是两个函数头定义,只要参数有一点不同就是不兼容的, 函数头不是类,没有任何继承兼容之类的说法
----------------------------------------------
-
作者:
男 janker (janker) ★☆☆☆☆ -
盒子活跃会员
2022/9/27 20:17:43
22楼: @roadrunner (roadrunner)
你可以试试10.3.3及以前的版本,原型就是兼容的,以前那么多版本,也没有见有人提出过这个问题。不可能是一句话BUG就可以说的通的。
对于泛型这个特别类型,人家编译器在10.3.3版本前就允许这样。
只是现在检测严格了。。。
----------------------------------------------
-
信息
登陆以后才能回复
Copyright © 2CCC.Com 盒子论坛 v2.1 版权所有 页面执行162.1094毫秒 RSS