До голяма степен съм запознат с модела Dispose за нефинализируеми типове, например типове, които обгръщат някакъв вид управляван ресурс, върху който искаме да се направи детерминистично почистване. Този вид типове обикновено не имплементират финализатор, тъй като той е напълно ненужен.
Въпреки това внедрявам обвивка на C# за естествен API, където обгръщам множество, свързани неуправляеми ресурси, и изглежда ще има нужда от множество класове, всеки от които прилага шаблона за финализирано изхвърляне. Проблемът е, че указанията за шаблона за изхвърляне казват, че finalizable A не трябва да разчита на finalizable B, което е точно това, от което се нуждая:
X НЕ осъществявайте достъп до финализиращи обекти в пътя на кода на финализатора, тъй като съществува значителен риск те вече да са финализирани.
Например, финализиращ обект A, който има препратка към друг финализиращ обект B, не може надеждно да използва B във финализатора на A, или обратното. Финализаторите се извикват в произволен ред (с изключение на слаба гаранция за подреждане за критично финализиране).
И така, ето моите ограничения:
- За да направя нещо, трябва да създам манипулатор "API".
- За да създам манипулатори на „дете“, трябва да осигуря манипулатора на API в извикването „създаване на дете“.
- И двата вида дръжки не могат да изтекат.
- Ако манипулаторът на API е затворен, всички негови дъщерни манипулатори са имплицитно затворени.
- За да затворя дъщерен манипулатор, трябва да осигуря дъщерния манипулатор и API манипулатора в основното извикване.
Родният API изглежда по следния начин:
APIHANDLE GizmoCreateHandle();
CHILDHANDLE GizmoCreateChildHandle( APIHANDLE apiHandle );
GizmoCloseHandle( APIHANDLE apiHandle );
GizmoCloseChildHandle( APIHANDLE apiHandle, CHILDHANDLE childHandle);
Наивният подход за това би бил две части:
- За манипулатора на API следвайте типичния модел SafeHandle.
- За дъщерния манипулатор, следвайте типичния модел SafeHandle, но го променете малко, тъй като дъщерният манипулатор се нуждае от препратка към API манипулатора, за да приложи своята замяна на ReleaseHandle - добавете метод, който дава API манипулатора на дъщерния SafeHandle след построено е.
Така че всичко ще изглежда така:
[DllImport( "gizmo.dll" )]
private static extern ApiSafeHandle GizmoCreateHandle();
[DllImport( "gizmo.dll" )]
private static extern void GizmoCloseHandle( IntPtr apiHandle );
[DllImport( "gizmo.dll" )]
private static extern ChildSafeHandle GizmoCreateChildHandle(ApiSafeHandle apiHandle);
[DllImport( "gizmo.dll" )]
private static extern void GizmoCloseChildHandle( ApiSafeHandle apiHandle, IntPtr childHandle );
[DllImport( "gizmo.dll" )]
private static extern void GizmoChildModify( ChildSafeHandle childHandle, int flag );
public class ApiSafeHandle : SafeHandle
{
public ApiSafeHandle() : base( IntPtr.Zero, true ) { }
public override bool IsInvalid
{
get { return this.handle == IntPtr.Zero; }
}
protected override bool ReleaseHandle()
{
GizmoCloseHandle( this.handle );
return true;
}
}
public class ChildSafeHandle : SafeHandle
{
private ApiSafeHandle apiHandle;
public ChildSafeHandle() : base( IntPtr.Zero, true ) { }
public override bool IsInvalid
{
get { return this.handle == IntPtr.Zero; }
}
public void SetParent( ApiSafeHandle handle )
{
this.apiHandle = handle;
}
// This method is part of the finalizer for SafeHandle.
// It access its own handle plus the API handle, which is also a SafeHandle
// According to MSDN, this violates the rules for finalizers.
protected override bool ReleaseHandle()
{
if ( this.apiHandle == null )
{
// We were used incorrectly - we were allocated, but never given
// the means to deallocate ourselves
return false;
}
else if ( this.apiHandle.IsClosed )
{
// Our parent was already closed, which means we were implicitly closed.
return true;
}
else
{
GizmoCloseChildHandle( apiHandle, this.handle );
return true;
}
}
}
public class GizmoApi
{
ApiSafeHandle apiHandle;
public GizmoApi()
{
this.apiHandle = GizmoCreateHandle();
}
public GizmoChild CreateChild()
{
ChildSafeHandle childHandle = GizmoCreateChildHandle( this.apiHandle );
childHandle.SetParent( this.apiHandle );
return new GizmoChild( childHandle );
}
}
public class GizmoChild
{
private ChildSafeHandle childHandle;
internal GizmoChild( ChildSafeHandle handle )
{
this.childHandle = handle;
}
public void SetFlags( int flags )
{
GizmoChildModify( this.childHandle, flags );
}
// etc.
}
Сега обаче имам недостатък - ReleaseHandle на моя ChildSafeHandle препраща към друг манипулатор, за да върши работата си. Сега имам два финализирани обекта за еднократна употреба, където финализаторът на единия зависи от другия. MSDN изрично казва, че финализаторите не трябва да разчитат на други обекти, които могат да бъдат финализирани, тъй като те може вече да са финализирани или ако .Net някога поддържа многонишково финализиране, те може да се финализират едновременно.
Какъв е правилният начин да направите това? Правилата позволяват ли ми достъп до финализиран обект A от финализатора на B, стига първо да тествам за валидност? MSDN не е ясен по този въпрос.