3

デフォルトの組み込み「コンポーネント ストリーミング システム」を使用する特定の方法で、プロパティの値がデフォルト値と等しい場合にその値が書き込まれないことが問題であることがわかりました。

次のスキームを考えてみましょう。

コンポーネントの特定の状態を保存するには、 WriteComponent ()ReadComponent( ) を使用します。この状態をプリセットと呼びます。コンポーネントには、setter を持つさまざまな Real 型のプロパティが含まれています。

プロパティがデフォルト値と等しい場合、プリセットにはその値が含まれないことがわかっています。

だから私たちのコンポーネントのために

  1. プロパティAFLoat0.0に設定します。
  2. プリセットをストリームに保存します (MyStream.WriteComponent(MyInstance))
  3. プロパティ AFLoat0.101に設定します
  4. プリセットをリロードします (MyStream.ReadComponent(MyInstance))

最後に、プリセットのリロード後、AFLoatの値は0.0であると予想していましたが、まだ0.101のままです。

バグの原因は、プロパティのデフォルト値がコンポーネント ストリームに書き込まれないことです。したがって、ステップ2では、プロパティは書き込まれず、ステップ 4 では読み取られません...かなり面倒ですよね!

プロパティのデフォルト値を強制的にコンポーネント ストリームに書き込む方法はありますか? 実際、実数型のプロパティに対する自家製の修正がありますが、根本的な問題を克服するためのよく知られた方法があるかどうかを知りたいです。

私のカスタム修正は、 ReadComponent()を呼び出す前に TypInfos を使用して、Real 型のプロパティを 0 にリセットすることです。

Procedure ResetFloatToNull(Const AnObject: TObject; Recursive: Boolean);
Var
  i,j: Integer;
  LList: PPropList;
Begin
  j := GetPropList( AnObject, LList);
  If j > 0 Then For i:= 0 To j-1 Do
    Case LList^[i].PropType^.Kind Of
    // floats with the object scope
    tkFloat: SetFloatProp(AnObject,LList^[i].Name,0);
    // floats with a subobject scope (IsSubComponent)
    tkClass: If Recursive Then
      ResetFloatToNull( TObject(GetOrdProp(AnObject,LList^[i])), True);
    End;
  FreeMem(LList);
End;

しかし、他の方法が存在しない場合、(暗黙的で二次的な質問): EMB はデフォルト値を放棄すべきではありませんか? IDE オブジェクト インスペクター (コンテキスト メニューの [継承にリセット]) には少し関心がありますが、コンポーネントのシリアライゼーション システムでさまざまな煩わしさを完全に引き起こします...

基本的な問題を理解していただければ幸いです。それ以外の場合は、小さな例を追加できます...


バグの小さなデモ (DH による最初の回答とコメント戦争の後に追加):

program Project1;
{$APPTYPE CONSOLE}
uses
  SysUtils,
  classes;

type
  TTestClass = class(TComponent)
  private
    ffloat1,ffloat2: single;
  published
    property float1: single read ffloat1 write ffloat1;
    property float2: single read ffloat2 write ffloat2;
  end;

var
  str: TMemoryStream;
  testclass: TTestClass;

begin
  testclass := TTestClass.Create(Nil);
  str := TMemoryStream.Create;
  //
  testclass.float1 := 0.31;
  testclass.float2 := 0.32;
  //
  testclass.float1 := 0.0;
  testclass.float2 := 0.2;
  str.WriteComponent(testclass);
  writeln( 'we have wrote a state when the prop 1 is equal to 0.0 and prop 2 is equal to 0.2');
  //
  testclass.float1 := 0.1;
  testclass.float2 := 0.3;
  writeln( 'then we set the prop 1 to 0.1 and prop 2 to 0.3');
  writeln('');
  //
  writeln( 'we reload the state saved when the prop 1 was equal to 0.0 and prop 2 to 0.2   and we get:');
  str.Position := 0;
  str.ReadComponent(testclass);
  writeln( Format( 'prop 1 equal to %.2f', [testclass.float1]));
  writeln( Format( 'prop 2 equal to %.2f', [testclass.float2]));
  //
  writeln('');
  writeln('prop 1 has not been restored because the default value 0.0 was not written');
  writeln('prop 2 has been restored because a non default value was written and read');
  //
  ReadLn;
  str.free;
  testclass.free;
end.
4

1 に答える 1

6

私はその質問に混乱していたことが判明しました。実際、実際の値のプロパティはデフォルトを持つことができないため、デフォルト プロパティはここでは関係ありません。

実際、値が 0 の場合、フレームワークは実際の値のプロパティをストリーミングしません。これは、実際の値のプロパティが実質的にハードコードされたデフォルトの 0 を持っていることを意味します。これは、ストリーミング フレームワークの巨大な設計上の欠陥のようです。

1コンポーネントのコンストラクターで値が割り当てられる実数値のプロパティを少し想像してみてください。これにより、プロパティに の値が割り当てられ、0その値が .dfm ファイルを介したラウンドトリップに耐えることができなくなります。

これを回避する方法はありますが、オーバーライドする必要がありますDefineProperties

type
  TMyComponent = class(TComponent)
  private
    FValue: Double;
    procedure WriteValue(Writer: TWriter);
  protected
    procedure DefineProperties(Filer: TFiler); override;
  public
    constructor Create(AOwner: TComponent); override;
  published
    property Value: Double read FValue write FValue;
  end;

constructor TMyComponent.Create(AOwner: TComponent);
begin
  inherited;
  FValue := 1.0;
end;

procedure TMyComponent.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineProperty('Value', nil, WriteValue, True);
end;

procedure TMyComponent.WriteValue(Writer: TWriter);
begin
  Writer.WriteDouble(FValue);
end;

VCL のバグは、Classesユニットの にありIsDefaultPropertyValueます。その関数内には、次のローカル関数があります。

function IsDefaultFloatProp: Boolean;
var
  Value: Extended;
begin
  Value := GetFloatProp(Instance, PropInfo);
  if AncestorValid then
    Result := Value = GetFloatProp(Ancestor, PropInfo)
  else
    Result := Value = 0;
end;

明らかに、この関数は次のように実装する必要があります。

function IsDefaultFloatProp: Boolean;
var
  Value: Extended;
begin
  Value := GetFloatProp(Instance, PropInfo);
  if AncestorValid then
    Result := Value = GetFloatProp(Ancestor, PropInfo)
  else
    Result := False;
end;

Int64Variantstringプロパティの扱いにも同様の不具合があるようです。これは非常に大きな欠陥であり、広く知られることを期待しています。SO、Emba フォーラム、およびいくつかの QC レポートで、このトピックについてすでに多くの議論がなされることを期待しています。そしてここに、黎明期の QC レポートがあります: QC#928 . そのレポートは 10 年以上前のものであり、瀕死の状態であるため、QC#109194を作成しました。

于 2012-10-01T11:23:46.487 に答える