febbraio 2005 Blog Posts
Un esempio semplice d'implementazione del pattern TemplateMethod all'interno del Framework .NET è costituito dalla renderizzazione dei server control web:
// ...namespace System.Web.UI.WebControls{ public class WebControl // ... { protected override void Render(HtmlTextWriter writer) { RenderBeginTag(writer); RenderContents(writer); RenderEndTag(writer); } public virtual void RenderBeginTag(HtmlTextWriter writer) { // ... } protected virtual void RenderContents(HtmlTextWriter writer) { // ... } public virtual void RenderEndTag(HtmlTextWriter writer) { // ... } // ... }}
I ruoli sono chiari:
la classe WebControl fa da AbstractClass (anche se non è abstract);
le classi che derivano da WebControl fanno da ConcreteClass;
il metodo Render fa da TemplateMethod;
i metodi RenderBeginTag, RenderContents e RenderEndTag fanno...
Dopo un non breve scambio di email con Paolo Arvati e in base a un suggerimento di Stefano Grevi, posto una nuova implementazione per il pattern:
public class FriendlyInheritableType{ internal FriendlyInheritableType() { if (this.GetType().Assembly != typeof(FriendlyInheritableType).Assembly) { throw new NotSupportedException(); } } public static FriendlyInheritableType NewInstance() { return new FriendlyInheritableType(); }}
Altrimenti, nella notazione del post precedente, FooInside deve o essere sealed, o implementare anche essa il pattern di FooBase, per non spezzare il limite di ereditarietà.
La soluzione che ho trovato per impedire l'ereditarietà cross-assembly ha profumo di pattern:
Come fare sì che la classe pubblica FooBase sia ereditabile esclusivamente all'interno di FooIn (l'assembly che la contiene) ma istanziabile anche all'esterno di questo?
// FooIn.cs// csc /t:library FooIn.csusing System;public class FooBase{ internal FooBase(){} public static FooBase CreateFooBase() { return new FooBase(); } public void DoSomething() { Console.WriteLine("FooBase"); }}// OKpublic class FooInside: FooBase{ public void DoSomethingElse() { Console.WriteLine("FooInside"); }}
Quindi riusciamo a derivare FooInside da FooBase (perché si trovano tutte e due in FooIn.dll) ma non è possibile derivare FooOutside da FooBase (perché si trova in un altro...
Sto pensando che sarebbe interessante un nuovo modificatore per una classe public che la rendi sealed solo fuori dal suo assembly...
Cosa ne pensate?
Se la classe Foo la compiliamo separatamente in un file "Foo.vb":
' Foo.vb<Microsoft.VisualBasic.CompilerServices.StandardModule()> _Public NotInheritable Class Foo Public Sub New() End SubEnd Class
con:
vbc /t:library Foo.vb
e proviamo a consumarla nel codice seguente:
' Test.vbClass Test Shared Sub Main() Dim f As Foo = New Foo End SubEnd Class
con:
vbc /r:Foo.dll Test.vb
otteniamo l'errore BC30371 ("Module 'Foo' cannot be used as a type.") come mostrato nel post precedente.
Se invece mettiamo tutto in un file, l'errore non appare più!
' TestFoo.vb<Microsoft.VisualBasic.CompilerServices.StandardModule()> _Public NotInheritable Class Foo Public Sub New() End SubEnd ClassClass Test Shared Sub Main() Dim f As Foo = New Foo End SubEnd Class...
Nel capitolo 7.7 delle specifiche VB .NET, si può leggere:
"A standard module is a type whose members are implicitly Shared and scoped to the declaration space of the standard module's containing namespace, rather than just to the standard module declaration itself. Standard modules may never be instantiated. It is an error to declare a variable of a standard module type."
ed è vero: provando a istanziare uno standard module in VB .NET si ottiene l'errore BC30371 ("Module '' cannot be used as a type"). Ma come traduce il compilatore di VB .NET in IL uno standard module? Per esempio, a questo...
Non sapevo che in VB .NET si potesse scrivere:
Dim C#
dove # è il così detto type character per un double (vedi le
specifiche, pp. 15-16).
Mi fa impressione vedere quella riga di codice :-)
using System;
class MyNullReferenceException: NullReferenceException
{
public override
string Message
{
get{return
"Ciao!";}
}
}
class Foo
{
static void
Main()
{
try
{
throw null
as
MyNullReferenceException;
}
catch(MyNullReferenceException
e)
{
Console.WriteLine(e.Message);
}
catch(NullReferenceException
e)
{
Console.WriteLine(e.Message);
}
}
}
Cosa viene visualizzato a console e perché?
A. Object reference not set to an instance of an object.
B. Ciao!
C. non compila
D. errore a run-time
Partendo da questo post di
Marco Russo e indagando un po' sulla modifica di
accessibilità nella sovrascrittura di un metodo virtuale in una relazione di
ereditarietà, ho scoperto che, ai metodi Foo in questo codice C++:
#using <mscorlib.dll>
using namespace
System;
public __gc
class A
{
protected: virtual
void Foo()
{
Console::WriteLine(S"A");
}
};
public __gc
class B: public
A
{
protected: virtual
void Foo()
{
Console::WriteLine(S"B");
}
};
public __gc
class C: public
B
{
public: virtual
void Foo()
{
Console::WriteLine(S"C");
}
};
corrispondono le seguenti signature IL:
.method family newslot virtual instance void Foo()
cil managed //
A
.method family virtual instance void Foo() cil managed //
B
.method public virtual instance void Foo() cil managed //
C
dove si nota in rosso il flag...
Grazie a questo
post di Colin Coller
ho scoperto, dopo quasi 2 anni dalla loro uscita, le 226 pagine di specifiche VB
.NET:
P. Vick, "Microsoft
Visual Basic .NET Language Specification, Version 7.1", Microsoft
Corporation (2003)
Da una prima occhiata mi sembrano, paragonandole con quelle
per C#, un po' troppo alla VB :-)