Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combining generics with different constraints

I have this atomic optimistic initializer class:

type
  Atomic<T: IInterface> = class
    type TFactory = reference to function: T;
    class function Initialize(var storage: T; factory: TFactory): T;
  end;

class function Atomic<T>.Initialize(var storage: T; factory: TFactory): T;
var
  tmpIntf: T;
begin
  if not assigned(storage) then begin
    tmpIntf := factory();
    if InterlockedCompareExchangePointer(PPointer(@storage)^, PPointer(@tmpIntf)^, nil) = nil then
      PPointer(@tmpIntf)^ := nil;
  end;
  Result := storage;
end;

Now I would like to implement the same pattern for objects.

type
  Atomic<T: class> = class
    type TFactory = reference to function: T;
    class function Initialize(var storage: T; factory: TFactory): T;
  end;

class function Atomic<T>.Initialize(var storage: T; factory: TFactory): T;
var
  tmpIntf: T;
begin
  if not assigned(storage) then begin
    tmpIntf := factory();
    if InterlockedCompareExchangePointer(PPointer(@storage)^, PPointer(@tmpIntf)^, nil) = nil then
      tmpIntf.Free;
  end;
  Result := storage;
end;

I can do those two in two separate classes, but I would really like to put both initializers under the same umbrella. IOW, I would ideally like to use this as

var
  o: TObject;
  i: IInterface;

Atomic<TObject>.Initialize(o, CreateObject);
Atomic<IInterface>.Initialize(i, CreateInterface);

I can't find any good solution for this. The only idea I got is to declare class as Atomic<T> (without constraints) and then somehow (don't yet know how) check the RTTI of T in runtime and proceed accordingly.

I don't like this idea much and I'm looking for a better approach.

like image 956
gabr Avatar asked Nov 30 '11 12:11

gabr


People also ask

Can a generic class have multiple constraints?

Multiple interface constraints can be specified. The constraining interface can also be generic.

How do you add generic constraints?

You can specify one or more constraints on the generic type using the where clause after the generic type name. The following example demonstrates a generic class with a constraint to reference types when instantiating the generic class.

What are the constraints in generics?

The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type.

Does generics increase code performance?

Using this, the programmer will improve the performance of an application. In addition to performance, generics provide type-safety and higher quality code because you get to reuse data processing algorithms without duplicating type-specific code. Generics are also known as Parametric polymorphism.


2 Answers

It seems you cannot specify constraints of the type "class or interface". Therefore the easiest solution seems to be to drop the constraint (you can enforce it at runtime, using RTTI).

For the RTTI approach, you can use the TypeInfo function:

uses
  ..., TypInfo;    

class function Atomic<T>.Initialize(var storage: T; factory: TFactory): T;
var
  tmpT: T;
begin
  if not assigned(PPointer(@storage)^) then begin
    tmpT := factory();
    if InterlockedCompareExchangePointer(PPointer(@storage)^, PPointer(@tmpT)^, nil) = nil then begin
      case PTypeInfo(TypeInfo(T))^.Kind of
        tkInterface:
          PPointer(@tmpT)^ := nil;
        tkClass:
          TObject(tmpT).Free;
        else
          raise Exception.Create('Atomic<T>.Initialize: Unsupported type');
      end;
    end;
  end;
  Result := storage;
end;
like image 155
Ondrej Kelle Avatar answered Oct 03 '22 00:10

Ondrej Kelle


One strongly-typed solution is to wrap both generic classes into another class to provide common namespace for the operation

type
  Atomic = class
    type
      Intf<T: IInterface> = class
        type TFactory = reference to function: T;
        class function Initialize(var storage: T; factory: TFactory): T;
      end;
      Obj<T: class> = class
        type TFactory = reference to function: T;
        class function Initialize(var storage: T; factory: TFactory): T;
      end;
  end;

Usage would then be:

var
  o: TObject;
  i: IInterface;

Atomic.Obj<TObject>.Initialize(o, CreateObject);
Atomic.Intf<IInterface>.Initialize(i, CreateInterface);
like image 45
gabr Avatar answered Oct 03 '22 01:10

gabr