{***************************************************************************} { } { Delphi.Mocks } { } { Copyright (C) 2011 Vincent Parrett } { } { http://www.finalbuilder.com } { } { } {***************************************************************************} { } { Licensed under the Apache License, Version 2.0 (the "License"); } { you may not use this file except in compliance with the License. } { You may obtain a copy of the License at } { } { http://www.apache.org/licenses/LICENSE-2.0 } { } { Unless required by applicable law or agreed to in writing, software } { distributed under the License is distributed on an "AS IS" BASIS, } { WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } { See the License for the specific language governing permissions and } { limitations under the License. } { } {***************************************************************************} unit Delphi.Mocks; interface {$I 'Delphi.Mocks.inc'} uses TypInfo, Rtti, Sysutils, {$IFDEF SUPPORTS_REGEX} System.RegularExpressions, {$ENDIF} Delphi.Mocks.WeakReference; type IWhen = interface; //Records the expectations we have when our Mock is used. We can then verify //our expectations later. IExpect = interface ['{8B9919F1-99AB-4526-AD90-4493E9343083}'] function Once : IWhen;overload; procedure Once(const AMethodName : string);overload; function Never : IWhen;overload; procedure Never(const AMethodName : string);overload; function AtLeastOnce : IWhen;overload; procedure AtLeastOnce(const AMethodName : string);overload; function AtLeast(const times : Cardinal) : IWhen;overload; procedure AtLeast(const AMethodName : string; const times : Cardinal);overload; function AtMost(const times : Cardinal) : IWhen;overload; procedure AtMost(const AMethodName : string; const times : Cardinal);overload; function Between(const a,b : Cardinal) : IWhen;overload; procedure Between(const AMethodName : string; const a,b : Cardinal);overload; function Exactly(const times : Cardinal) : IWhen;overload; procedure Exactly(const AMethodName : string; const times : Cardinal);overload; function Before(const AMethodName : string) : IWhen;overload; procedure Before(const AMethodName : string; const ABeforeMethodName : string);overload; function After(const AMethodName : string) : IWhen;overload; procedure After(const AMethodName : string; const AAfterMethodName : string);overload; end; IWhen = interface ['{A8C2E07B-A5C1-463D-ACC4-BA2881E8419F}'] function When : T; end; /// This is the definition for an anonymous function you can pass into /// WillExecute. The args array will be the arguments passed to the method /// called on the Mock. If the method returns a value then your anon func must /// return that.. and the return type must match. The return type is passed in /// so that you can ensure tha. TExecuteFunc = reference to function (const args : TArray; const ReturnType : TRttiType) : TValue; IStubSetup = interface ['{3E6AD69A-11EA-47F1-B5C3-63F7B8C265B1}'] function GetBehaviorMustBeDefined : boolean; procedure SetBehaviorMustBeDefined(const value : boolean); function GetAllowRedefineBehaviorDefinitions : boolean; procedure SetAllowRedefineBehaviorDefinitions(const value : boolean); //set the return value for a method when called with the parameters specified on the When function WillReturn(const value : TValue) : IWhen; overload; //set the return value for a method when called with the parameters specified on the When //AllowNil flag allow to define: returning nil value is allowed or not. function WillReturn(const value : TValue; const AllowNil: Boolean) : IWhen; overload; //set the nil as return value for a method when called with the parameters specified on the When function WillReturnNil : IWhen; //Will exedute the func when called with the specified parameters function WillExecute(const func : TExecuteFunc) : IWhen;overload; //will always execute the func no matter what parameters are specified. procedure WillExecute(const AMethodName : string; const func : TExecuteFunc);overload; //set the default return value for a method when it is called with parameter values we //haven't specified procedure WillReturnDefault(const AMethodName : string; const value : TValue); //set the Exception class that will be raised when the method is called with the parmeters specified function WillRaise(const exceptionClass : ExceptClass; const message : string = '') : IWhen;overload; //This method will always raise an exception.. this behavior will trump any other defined behaviors procedure WillRaise(const AMethodName : string; const exceptionClass : ExceptClass; const message : string = '');overload; //set the Exception class that will be raised when the method is called with the parmeters specified function WillRaiseWhen(const exceptionClass: ExceptClass; const message: string = ''): IWhen; //If true, calls to methods for which we have not defined a behavior will cause verify to fail. property BehaviorMustBeDefined : boolean read GetBehaviorMustBeDefined write SetBehaviorMustBeDefined; //If true, it is possible to overwrite a already defined behaviour. property AllowRedefineBehaviorDefinitions: boolean read GetAllowRedefineBehaviorDefinitions write SetAllowRedefineBehaviorDefinitions; end; //We use the Setup to configure our expected behaviour rules and to verify //that those expectations were met. IMockSetup = interface(IStubSetup) ['{D6B21933-BF51-4937-877E-51B59A3B3268}'] //Set Expectations for methods function Expect : IExpect; end; IStubProxy = interface ['{578BAF90-4155-4C0F-AAED-407057C6384F}'] function Setup : IStubSetup; function Proxy : T; end; {$IFOPT M+} {$M-} {$DEFINE ENABLED_M+} {$ENDIF} IProxy = interface(IWeakReferenceableObject) ['{C97DC7E8-BE99-46FE-8488-4B356DD4AE29}'] function ProxyInterface : IInterface; function ProxyFromType(const ATypeInfo : PTypeInfo) : IProxy; procedure AddImplement(const AProxy : IProxy; const ATypeInfo : PTypeInfo); function QueryImplementedInterface(const IID: TGUID; out Obj): HRESULT; stdcall; procedure SetParentProxy(const AProxy : IProxy); function SupportsIInterface: Boolean; end; {$IFDEF ENABLED_M+} {$M+} {$ENDIF} //used by the mock - need to find another place to put this.. circular references //problem means we need it here for now. IProxy = interface(IProxy) ['{1E3A98C5-78BA-4D65-A4BA-B6992B8B4783}'] function Setup : IMockSetup; function Proxy : T; end; IAutoMock = interface ['{9C7113DF-6F93-496D-A223-61D30782C7D8}'] function Mock(const ATypeInfo : PTypeInfo) : IProxy; procedure Add(const ATypeName : string; const AMock: IProxy); end; TStub = record private FProxy : IStubProxy; FAutomocker : IAutoMock; public class operator Implicit(const Value: TStub): T; function Setup : IStubSetup; function Instance : T; function InstanceAsValue : TValue; class function Create: TStub; overload; static; class function Create(const ACreateObjectFunc: TFunc): TStub; overload; static; // explicit cleanup. Not sure if we really need this. procedure Free; end; //We use a record here to take advantage of operator overloading, the Implicit //operator allows us to use the mock as the interface without holding a reference //to the mock interface outside of the mock. TMock = record private FProxy : IProxy; FCreated : Boolean; FAutomocker : IAutoMock; procedure CheckCreated; public class operator Implicit(const Value: TMock): T; class function Create(const AAutoMock: IAutoMock; const ACreateObjectFunc: TFunc): TMock; overload; static; function Setup : IMockSetup; overload; function Setup : IMockSetup; overload; //Verify that our expectations were met. procedure Verify(const message : string = ''); overload; procedure Verify(const message : string = ''); overload; procedure VerifyAll(const message : string = ''); function CheckExpectations: string; procedure Implement; overload; function Instance : T; overload; function Instance : I; overload; function InstanceAsValue : TValue; overload; function InstanceAsValue : TValue; overload; class function Create: TMock; overload; static; class function Create(const ACreateObjectFunc: TFunc): TMock; overload; static; //Explicit cleanup. Not sure if we really need this. procedure Free; end; TAutoMockContainer = record private FAutoMocker : IAutoMock; public function Mock : TMock; overload; procedure Mock(const ATypeInfo : PTypeInfo); overload; class function Create : TAutoMockContainer; static; end; /// Used for defining permissable parameter values during method setup. /// Inspired by Moq ItRec = record var ParamIndex : cardinal; constructor Create(const AParamIndex : Integer); function IsAny() : T ; function Matches(const predicate: TPredicate) : T; function IsNotNil : T; function IsEqualTo(const value : T) : T; function IsInRange(const fromValue : T; const toValue : T) : T; function IsIn(const values : TArray) : T; overload; function IsIn(const values : IEnumerable) : T; overload; function IsNotIn(const values : TArray) : T; overload; function IsNotIn(const values : IEnumerable) : T; overload; {$IFDEF SUPPORTS_REGEX} //XE2 or later function IsRegex(const regex : string; const options : TRegExOptions = []) : string; {$ENDIF} function AreSamePropertiesThat(const Value: T): T; function AreSameFieldsThat(const Value: T): T; function AreSameFieldsAndPropertiedThat(const Value: T): T; end; TComparer = class public class function CompareFields(Param1, Param2: T): Boolean; class function CompareMembers(Members: TArray; Param1, Param2: T2): Boolean; class function CompareProperties(Param1, Param2: T): Boolean; end; //Exception Types that the mocks will raise. EMockException = class(Exception); EMockProxyAlreadyImplemented = class(EMockException); EMockSetupException = class(EMockException); EMockNoRTTIException = class(EMockException); EMockNoProxyException = class(EMockException); EMockVerificationException = class(EMockException); TTypeInfoHelper = record helper for TTypeInfo function NameStr : string; inline; end; function It(const AParamIndx : Integer) : ItRec; function It0 : ItRec; function It1 : ItRec; function It2 : ItRec; function It3 : ItRec; function It4 : ItRec; function It5 : ItRec; function It6 : ItRec; function It7 : ItRec; function It8 : ItRec; function It9 : ItRec; implementation uses Classes, Generics.Defaults, Delphi.Mocks.Utils, Delphi.Mocks.Interfaces, Delphi.Mocks.Proxy, Delphi.Mocks.ObjectProxy, Delphi.Mocks.ParamMatcher, Delphi.Mocks.AutoMock, Delphi.Mocks.Validation, Delphi.Mocks.Helpers; procedure TMock.CheckCreated; var pInfo : PTypeInfo; begin pInfo := TypeInfo(T); if not FCreated then raise EMockException.CreateFmt('Create for TMock<%s> was not called before use.', [pInfo.Name]); if (FProxy = nil) then raise EMockException.CreateFmt('Internal Error : Internal Proxy for TMock<%s> was nil.', [pInfo.Name]); end; function TMock.CheckExpectations: string; var su : IMockSetup; v : IVerify; begin CheckCreated; if Supports(FProxy.Setup,IVerify,v) then Result := v.CheckExpectations else raise EMockException.Create('Could not cast Setup to IVerify interface!'); end; class function TMock.Create: TMock; begin Result := Create(nil); end; class function TMock.Create(const ACreateObjectFunc: TFunc): TMock; begin Result := Create(nil, ACreateObjectFunc); end; class function TMock.Create(const AAutoMock: IAutoMock; const ACreateObjectFunc: TFunc): TMock; var proxy : IInterface; pInfo : PTypeInfo; begin //Make sure that we start off with a clean mock FillChar(Result, SizeOf(Result), 0); //By default we don't auto mock TMock. It changes when TAutoMock is used. Result.FAutomocker := AAutoMock; pInfo := TypeInfo(T); //Raise exceptions if the mock doesn't meet the requirements. TMocksValidation.CheckMockType(pInfo); case pInfo.Kind of //Create our proxy object, which will implement our object T tkClass : proxy := TObjectProxy.Create(ACreateObjectFunc, Result.FAutomocker, false); //Create our proxy interface object, which will implement our interface T tkInterface : proxy := TProxy.Create(Result.FAutomocker, false); end; //Push the proxy into the result we are returning. if proxy.QueryInterface(GetTypeData(TypeInfo(IProxy)).Guid, Result.FProxy) <> 0 then //TODO: This raise seems superfluous as the only types which are created are controlled by us above. They all implement IProxy raise EMockNoProxyException.Create('Error casting to interface ' + pInfo.NameStr + ' , proxy does not appear to implememnt IProxy'); //The record has been created! Result.FCreated := True; end; procedure TMock.Free; begin CheckCreated; FProxy := nil; FAutomocker := nil; end; procedure TMock.Implement; var proxy : IProxy; pInfo : PTypeInfo; begin CheckCreated; if FProxy is TObjectProxy then raise ENotSupportedException.Create('Adding interface implementation to non interfaced objects not supported at this time'); pInfo := TypeInfo(I); TMocksValidation.CheckMockInterface(pInfo); proxy := TProxy.Create; FProxy.AddImplement(proxy, pInfo); end; class operator TMock.Implicit(const Value: TMock): T; begin Value.CheckCreated; result := Value.FProxy.Proxy; end; function TMock.Instance : T; begin CheckCreated; result := FProxy.Proxy; end; function TMock.Instance: I; var prox : IInterface; proxyI : IProxy; pInfo : PTypeInfo; begin result := nil; CheckCreated; //Does the proxy we have, or any of its children support a proxy for the passed //in interface type? pInfo := TypeInfo(I); prox := FProxy.ProxyFromType(pInfo); if prox = nil then raise EMockException.CreateFmt('Mock does not implement [%s]', [pInfo.NameStr]); if (prox = nil) or (not Supports(prox, IProxy, proxyI)) then raise EMockException.CreateFmt('Proxy for [%s] does not support [IProxy].', [pInfo.NameStr]); //Return the interface for the requested implementation. result := proxyI.Proxy; end; function TMock.InstanceAsValue: TValue; begin CheckCreated; result := TValue.From(Self); end; function TMock.InstanceAsValue: TValue; begin CheckCreated; result := TValue.From(Self.Instance); end; function TMock.Setup: IMockSetup; begin CheckCreated; result := FProxy.Setup; end; {$O-} function TMock.Setup: IMockSetup; var setup : IProxy; pInfo : PTypeInfo; pMockSetupInfo : PTypeInfo; begin CheckCreated; //We have to ask for the proxy who owns the implementation of the interface/object //in question. The reason for this it that all proxies implement IProxy and //therefore we will just get the first proxy always. //E.g. IProxy and IProxy have the same GUID. Makes //generic interfaces hard to use. pInfo := TypeInfo(I); //Get the proxy which implements setup := FProxy.ProxyFromType(pInfo); //If nill is returned then we don't implement the defined type. if setup = nil then raise EMockNoProxyException.CreateFmt('[%s] is not implement.', [pInfo.NameStr]); //Now get it as the mocksetup that we requrie. Note that this doesn't ensure //that I is actually implemented as all proxies implment IMockSetup. This //is what we only return the error that IMockSetup isn't implemented. if not Supports(setup, IMockSetup, result) then begin pMockSetupInfo := TypeInfo(IMockSetup); raise EMockNoProxyException.CreateFmt('[%s] Proxy does not implement [%s]', [pInfo.NameStr, pMockSetupInfo.NameStr]); end; end; {$O+} procedure TMock.Verify(const message: string); var v : IVerify; begin CheckCreated; if Supports(FProxy.Setup, IVerify, v) then v.Verify(message) else raise EMockException.Create('Could not cast Setup to IVerify interface!'); end; {$O-} procedure TMock.Verify(const message: string); var prox : IInterface; interfaceV : IVerify; pInfo : PTypeInfo; begin CheckCreated; //Does the proxy we have, or any of its children support a proxy for the passed //in interface type? pInfo := TypeInfo(I); prox := FProxy.ProxyFromType(pInfo); if (prox = nil) or (not Supports(prox, IVerify, interfaceV)) then raise EMockException.Create('Could not cast Setup to IVerify interface!'); interfaceV.Verify(message); end; {$O+} procedure TMock.VerifyAll(const message: string); var interfaceV : IVerify; begin CheckCreated; if Supports(FProxy.Setup, IVerify, interfaceV) then interfaceV.VerifyAll(message) else raise EMockException.Create('Could not cast Setup to IVerify interface!'); end; { TStub } class function TStub.Create(): TStub; begin result := TStub.Create(nil); end; class function TStub.Create(const ACreateObjectFunc: TFunc): TStub; var proxy : IInterface; pInfo : PTypeInfo; begin //Make sure that we start off with a clean mock FillChar(Result, SizeOf(Result), 0); //By default we don't auto mock TMock. It changes when TAutoMock is used. Result.FAutomocker := nil; pInfo := TypeInfo(T); if not (pInfo.Kind in [tkInterface,tkClass]) then raise EMockException.Create(pInfo.NameStr + ' is not an Interface or Class. TStub supports interfaces and classes only'); case pInfo.Kind of //NOTE: We have a weaker requirement for an object proxy opposed to an interface proxy. //NOTE: Object proxy doesn't require more than zero methods on the object. tkClass : begin //Check to make sure we have if not CheckClassHasRTTI(pInfo) then raise EMockNoRTTIException.Create(pInfo.NameStr + ' does not have RTTI, specify {$M+} for the object to enabled RTTI'); //Create our proxy object, which will implement our object T proxy := TObjectProxy.Create(ACreateObjectFunc, Result.FAutomocker, true); end; tkInterface : begin //Check to make sure we have if not CheckInterfaceHasRTTI(pInfo) then raise EMockNoRTTIException.Create(pInfo.NameStr + ' does not have RTTI, specify {$M+} for the interface to enabled RTTI'); //Create our proxy interface object, which will implement our interface T proxy := TProxy.Create(Result.FAutomocker, true); end; else raise EMockException.Create('Invalid type kind T'); end; //Push the proxy into the result we are returning. if proxy.QueryInterface(GetTypeData(TypeInfo(IStubProxy)).Guid, Result.FProxy) <> 0 then //TODO: This raise seems superfluous as the only types which are created are controlled by us above. They all implement IProxy raise EMockNoProxyException.Create('Error casting to interface ' + pInfo.NameStr + ' , proxy does not appear to implememnt IProxy'); end; procedure TStub.Free; begin FProxy := nil; end; class operator TStub.Implicit(const Value: TStub): T; begin result := Value.FProxy.Proxy; end; function TStub.Instance: T; begin result := FProxy.Proxy; end; function TStub.InstanceAsValue: TValue; begin result := TValue.From(Self); end; function TStub.Setup: IStubSetup; begin result := FProxy.Setup; end; { TTypeInfoHelper } function TTypeInfoHelper.NameStr: string; begin {$IFNDEF NEXTGEN} result := string(Self.Name); {$ELSE} result := Self.NameFld.ToString; {$ENDIF} end; { TAutoMockContainer } class function TAutoMockContainer.Create: TAutoMockContainer; begin FillChar(Result, SizeOf(Result), 0); Result.FAutoMocker := TAutoMock.Create; end; procedure TAutoMockContainer.Mock(const ATypeInfo: PTypeInfo); begin FAutoMocker.Mock(ATypeInfo); end; function TAutoMockContainer.Mock: TMock; var mock : TMock; pInfo : PTypeInfo; begin pInfo := TypeInfo(T); mock := TMock.Create(FAutoMocker, nil); FAutoMocker.Add(pInfo.NameStr, mock.FProxy); result := mock; end; { It } function ItRec.AreSameFieldsAndPropertiedThat(const Value: T): T; begin Result := Value; TMatcherFactory.Create(ParamIndex, function(Param: T): Boolean begin Result := TComparer.CompareFields(Param, Value) and TComparer.CompareProperties(Param, Value); end); end; function ItRec.AreSameFieldsThat(const Value: T): T; begin Result := Value; TMatcherFactory.Create(ParamIndex, function(Param: T): Boolean begin Result := TComparer.CompareFields(Param, Value); end); end; function ItRec.AreSamePropertiesThat(const Value: T): T; begin Result := Value; TMatcherFactory.Create(ParamIndex, function(Param: T): Boolean begin Result := TComparer.CompareProperties(Param, Value); end); end; constructor ItRec.Create(const AParamIndex : Integer); begin ParamIndex := AParamIndex; end; function ItRec.IsAny: T; begin result := Default(T); TMatcherFactory.Create(ParamIndex, function(value : T) : boolean begin result := true; end); end; function ItRec.IsEqualTo(const value : T) : T; begin Result := Value; TMatcherFactory.Create(ParamIndex, function(param : T) : boolean var comparer : IEqualityComparer; begin comparer := TEqualityComparer.Default; result := comparer.Equals(param,value); end); end; function ItRec.IsIn(const values: TArray): T; begin result := Default(T); TMatcherFactory.Create(ParamIndex, function(param : T) : boolean var comparer : IEqualityComparer; value : T; begin result := false; comparer := TEqualityComparer.Default; for value in values do begin result := comparer.Equals(param,value); if result then exit; end; end); end; function ItRec.IsIn(const values: IEnumerable): T; begin result := Default(T); TMatcherFactory.Create(ParamIndex, function(param : T) : boolean var comparer : IEqualityComparer; value : T; begin result := false; comparer := TEqualityComparer.Default; for value in values do begin result := comparer.Equals(param,value); if result then exit; end; end); end; function ItRec.IsInRange(const fromValue, toValue: T): T; begin result := Default(T); end; function ItRec.IsNotIn(const values: TArray): T; begin result := Default(T); TMatcherFactory.Create(ParamIndex, function(param : T) : boolean var comparer : IEqualityComparer; value : T; begin result := true; comparer := TEqualityComparer.Default; for value in values do begin if comparer.Equals(param,value) then exit(false); end; end); end; function ItRec.IsNotIn(const values: IEnumerable): T; begin result := Default(T); TMatcherFactory.Create(ParamIndex, function(param : T) : boolean var comparer : IEqualityComparer; value : T; begin result := true; comparer := TEqualityComparer.Default; for value in values do begin if comparer.Equals(param,value) then exit(false); end; end); end; function ItRec.IsNotNil: T; begin result := Default(T); TMatcherFactory.Create(ParamIndex, function(param : T) : boolean var comparer : IEqualityComparer; begin comparer := TEqualityComparer.Default; result := not comparer.Equals(param,Default(T)); end); end; function ItRec.Matches(const predicate: TPredicate): T; begin result := Default(T); TMatcherFactory.Create(ParamIndex, predicate); end; //class function It.ParamIndex: integer; //begin // result := 0; //end; {$IFDEF SUPPORTS_REGEX} //XE2 or later function ItRec.IsRegex(const regex : string; const options : TRegExOptions) : string; begin result := ''; TMatcherFactory.Create(ParamIndex, function(param : string) : boolean begin result := TRegEx.IsMatch(param,regex,options) end); end; {$ENDIF} function It(const AParamIndx : Integer) : ItRec; begin result := ItRec.Create(AParamIndx); end; function It0 : ItRec; begin result := ItRec.Create(0); end; function It1 : ItRec; begin result := ItRec.Create(1); end; function It2 : ItRec; begin result := ItRec.Create(2); end; function It3 : ItRec; begin result := ItRec.Create(3); end; function It4 : ItRec; begin result := ItRec.Create(4); end; function It5 : ItRec; begin result := ItRec.Create(5); end; function It6 : ItRec; begin result := ItRec.Create(6); end; function It7 : ItRec; begin result := ItRec.Create(7); end; function It8 : ItRec; begin result := ItRec.Create(8); end; function It9 : ItRec; begin result := ItRec.Create(9); end; { TComparer } class function TComparer.CompareFields(Param1, Param2: T): Boolean; var RTTI: TRttiContext; begin RTTI := TRttiContext.Create; Result := CompareMembers(RTTI.GetType(TypeInfo(T)).GetFields, Param1, Param2); end; class function TComparer.CompareMembers(Members: TArray; Param1, Param2: T2): Boolean; var PublicMember: TRttiMember; Instance1, Instance2, MemberValue1, MemberValue2: TValue; MemberType: TTypeKind; begin Instance1 := TValue.From(Param1); Instance2 := TValue.From(Param2); Result := SameValue(Instance1, Instance2); if not Result and not Instance1.IsEmpty and not Instance2.IsEmpty then begin Result := True; for PublicMember in Members do if PublicMember.Visibility in [mvPublic, mvPublished] then begin if PublicMember is TRttiProperty then begin MemberValue1 := TRttiProperty(PublicMember).GetValue(Instance1.AsPointer); MemberValue2 := TRttiProperty(PublicMember).GetValue(Instance2.AsPointer); MemberType := TRttiProperty(PublicMember).PropertyType.TypeKind; end else begin MemberValue1 := TRttiField(PublicMember).GetValue(Instance1.AsPointer); MemberValue2 := TRttiField(PublicMember).GetValue(Instance2.AsPointer); MemberType := TRttiField(PublicMember).FieldType.TypeKind; end; if MemberType = tkClass then Result := Result and CompareMembers(MemberValue1.RttiType.GetFields, MemberValue1.AsObject, MemberValue2.AsObject) and CompareMembers(MemberValue1.RttiType.GetProperties, MemberValue1.AsObject, MemberValue2.AsObject) else Result := Result and SameValue(MemberValue1, MemberValue2); end; end; end; class function TComparer.CompareProperties(Param1, Param2: T): Boolean; var RTTI: TRttiContext; begin RTTI := TRttiContext.Create; Result := CompareMembers(RTTI.GetType(TypeInfo(T)).GetProperties, Param1, Param2); end; end.