Web Log di Adrian Florea

"You know you've achieved perfection in design, not when you have nothing more to add, but when you have nothing more to take away." Antoine de Saint-Exupery
posts - 427, comments - 867, trackbacks - 93

My Links

Archives

Post Categories

Image Galleries

.RO Blogs

.RO People

.RO Sites

Blogs

Furls

Links

vinCitori

giovedì 5 giugno 2008

I vostri migliori post, da oggi anche in rumeno

Oggi ho aperto il mio primo blog in rumeno, a questo indirizzo su RONUA. Per cominciare, ho deciso di tradurre in rumeno i post piu' interessanti che trovero' nelle varie community italiane, per far conoscere ai miei connazionali il grande spirito che ho conosciuto e mi avete regalato negli anni passati con voi. Ogni post sara' fornito ovviamente dal link al post originale, spero di avere il vostro consenso per la traduzione. E chissa', magari si stringono amicizie nuove, etc. Il blog su UGI non finisce qui, salvo completa mancanza di ispirazione :-)

posted @ giovedì 5 giugno 2008 22.32 | Feedback (4) | Filed Under [ Adrian Varie Voi GUISA RONUA ]

mercoledì 28 maggio 2008

Lazy loading in una riga

Carino questo modo proposto da Frank Quednau di implementare il lazy loading utilizzando l'operatore di null coalescing (ECMA-334, 14.12) e il fatto che il risultato di un'assegnazione e' il valore assegnato all'operando sinistro (ECMA-334, 14.14.1):

internal sealed class PersonProxy : Person
{
    public override AddressCollection Addresses
    {
        get
        {
            return base.Addresses ?? (base.Addresses = new PersonDataMapper().GetAddressesByCode(this.Code));
        }
        set
        {
            base.Addresses = value;
        }
    }
}

A me sembra molto espressivo.

posted @ mercoledì 28 maggio 2008 22.04 | Feedback (3) | Filed Under [ Pattern Dappertutto ]

domenica 18 maggio 2008

Quiz Sharp #70

Senza utilizzare stringhe, caratteri, numeri (literals) o file esterni, si chiede di scrivere un programma in C# 3.0 che stampi a console la definizione compilabile di un tipo non vuoto.

posted @ domenica 18 maggio 2008 6.22 | Feedback (12) | Filed Under [ Quiz Sharp Test Sharp ]

domenica 4 maggio 2008

Best practice Path.Combine

Se vogliamo che il nostro codice giri anche su Mono, dobbiamo utilizzare:

Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "aaa.bbb")

al posto di:

AppDomain.CurrentDomain.BaseDirectory + "aaa.bbb"

perche' su Mono, BaseDirectory ritorna una stringa che non finisce in Path.DirectorySeparatorChar, mentre su CLR si'. E questo va anche in generale, quando costruiamo il path da piu' pezzi, non solo nel caso della BaseDirectory. Per esempio, chi utilizza fyiReporting RDL Project su Mono, dovrebbe modificare la riga 81 nel file Runtime/RdlEngineConfig.cs nei sorgenti del progetto e ricompilare, da:

file = dir + "RdlEngineConfig.xml";

a:

file = Path.Combine(dir, "RdlEngineConfig.xml");

perche' la stringa dir, per come e' stata costruita, su CLR finisce in Path.DirectorySeparatorChar, mentre su Mono no. In SLAR 1 alla pagina 360, Joel Marcey aggiunge questa nota sulla storia del metodo Combine:

The Combine method had an interesting ride in the standardization process. It was originally part of the Path class, then removed because it was thought to be too platform-specific, then added back because it was decided it was not any more platform-specific than any of the other methods in this class.

posted @ domenica 4 maggio 2008 21.44 | Feedback (1) | Filed Under [ Carillon .NET Pattern Dappertutto Bugs? ]

sabato 3 maggio 2008

Un'API generico a provider per i metadata dei vari ORM

Via questo post di Frans Bouma ho scoperto sotto il namespace System.Web.DynamicData.ModelProviders dell'assembly System.Web.DynamicData.dll che arriva con l'ultima release della preview di ASP.NET Dynamic Data, un'API generico composto da 4 provider per i metadata dei vari ORM (non solo Microsoft): DataModelProvider, TableProvider, ColumnProvider ed AssociationProvider. Questo unifica in buona misura le varie API che espongono i metadata degli ORM, per esempio MetaTable, MetaDataMember, MetaAssociation nel caso di LINQ to SQL, oppure quella piu' complessa dell'Entity Framework: EntitySet, EdmMember, NavigationProperty, etc. Frans ha gia' scritto un model provider per il suo LLBLGen Pro, sarebbe bellissimo averne uno anche per NHibernate. E poi mi piacerebbe in futuro vedere questa API staccata dal ASP.NET Dynamics Data con cui non dovrebbe avere tanto in comune.

posted @ sabato 3 maggio 2008 1.53 | Feedback (0) | Filed Under [ Carillon .NET Pattern Dappertutto VSX ]

mercoledì 16 aprile 2008

RONUA Galati

E' dall'anno scorso, da quando sono tornato a casa in Romania, che sogno la costituzione di una filiale locale dello user group rumeno di .NET, RONUA. Ed ecco che oggi, il presidente di RONUA, Aurelian Popa, mi incarica con l'organizzazione della nuova community. Lo spazio per gli workshop sara' messo a disposizione grazie all'entusiasmo e all'appoggio di Catalin Arama nell'attrezzatissima sala conferenze del Parco Software di Galati. Una settantina di libri personali li metto a disposizione io per creare una piccola libreria .NET (nella stanza 408 dove lavoro presso lo stesso Parco Software). Sto pensando che potrei trovare degli sponsor per pagare alloggio e trasporto fino in Romania se qualcuno di voi avra' voglia di tenere una sessione .NET qui in Romania, nella mia citta' sul Danubio, gemellata con Ancona, Brindisi, Jesi e Salerno :-) Vi terro' aggiornati sugli sviluppi della nuova community.

posted @ mercoledì 16 aprile 2008 20.49 | Feedback (9) | Filed Under [ Adrian Varie RONUA ]

martedì 8 aprile 2008

Quiz Sharp #69

Scrivete una proprieta' in cui il body del getter non contenga return o throw.

posted @ martedì 8 aprile 2008 8.22 | Feedback (9) | Filed Under [ Quiz Sharp ]

lunedì 17 marzo 2008

Brian Grunkemeyer sulla storia del TypeCode e dell'interfaccia IConvertible

Visto l'interesse che ha suscitato l'ultimo post, ho scritto a Brad Abrams chiedendo conferma per la mia supposizione e dettagli sulla storia dell'interfaccia IValue, lui mi risponde subito indirizzandomi a Brian Grunkemeyer e stamattina trovo nella mia casella email, scritto da Brian, questo splendido pezzo della storia di .NET:

This is a good question. I was digging through the history of this file to see if I could figure out what happened, and it’s not clear. We’ve had this “hole” in the TypeCode enum since October of 2000, and I can’t find an older set of bits. But, I’m sure that comment in IConvertible is right – this used to be TimeSpan. For TimeSpan, it’s possible we thought it would be interesting for a while, then we realized that frankly not that many people need to convert a Decimal to a TimeSpan, then removed it.

You might ask why we didn’t “fix” the enum when we removed whichever of these values we had originally added. It turns out that whenever we have a breaking change internally, we need to recompile all the code that might possibly depend on the removed or changed public surface area. For us, that would mean rebuilding everything that might have referred to TypeCode.String, whose value would have changed from 18 to 17. While we do go through that process internally in DevDiv, it is costly & painful for us. Back then with some underdeveloped internal processes, it usually took 2 weeks, then even longer if your change requires a rebuilt compiler to be checked in as a new “safe” build of a compiler. Back in 2000, we were racing to ship as soon as possible (even though we didn’t ship for another year or two), so we wanted to limit churn like this. It’s sometimes easier to not deal with that level of churn in the product.

For your other question about IValue, that was the original name for IConvertible. We exposed a Variant type to match OLE Automation’s VARIANT type on the native side, for COM Interop reasons. The VB team actually was thinking that Variant should be conceptually the root of their object hierarchy (at least in terms of how types were exposed in VB 7), but that seemed a little silly. Fortunately, Anders Hejlsberg convinced them to use Object instead and we’d expose IConvertible on the base data types to allow VB users to easily change from one data type to another. So we marked Variant internal & we only use it in a handful of places internally for COM-related functionality.

Grazie ragazzi!

posted @ lunedì 17 marzo 2008 18.24 | Feedback (3) | Filed Under [ Un po' di storia Carillon .NET ]

mercoledì 12 marzo 2008

Sull'enum TypeCode e sull'interfaccia fantoma IValue

Oggi Raf sul messenger mi chiede se conosco un modo piu' diretto per capire se un tipo sia primitivo oppure String o DateTime (questo per evitare degli if...). Pensandoci un po', arrivo a questa soluzione:

(int)Type.GetTypeCode(type) > 2

dove type e' il tipo in causa. Lui prova e mi dice che va benissimo mentre io gia' sto pensando di bloggare questa riga di codice :-) L'enum TypeCode infatti, contiene nella sua lista di valori maggiori a 2 tutti i 12 tipi primitivi piu' il DateTime e lo String, proprio quello che voleva Raf. A questo punto gli chiedo come denominare questa categoria di tipi? Primitivi non sono, built-in (ECMA-335, Partition I, 8.2.2) nemmeno, boh... fondamentali? Qui Raf si ricorda che "in una alpha erano refclass adesso non piu'". E poi fa questa osservazione giusta:

Raffaele MVP says (4:45 PM):
io comunque metterei >2 e <18
non si sa mai che aggiungano altre enum in futuro
<drian says (4:46 PM):
hai visto che manca 17?
Raffaele MVP says (4:46 PM):
non ci ho fatto caso
<drian says (4:47 PM):
in questo caso, dovrei fare anche != 17, perche' il 17 per adesso manca
e diventa un po' una schifezza
Raffaele MVP says (4:48 PM):
a questo punto puoi legarlo ad una versione specifica di quell'assembly ma poi non funziona più automaticamente
Raffaele MVP says (4:49 PM):
o ancora metterlo typesafe
>=boolean <=string
ma c'è sempre il 17 a rompere

Spulciando poi i sorgenti di Rotor, trovo questo commento nel file dell'interfaccia System.IConvertible

// The IValue interface represents an object that contains a value. This
// interface is implemented by the following types in the System namespace:
// Boolean, Char, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64,
// Single, Double, Decimal, DateTime, TimeSpan, and String. The interface may
// be implemented by other types that are to be considered values.

Cosa notate? Esattamente l'elenco di tipi che interessava Raf, nell'ordine di TypeCode e... in piu' il TimeSpan tra il DateTime = 16 e lo String = 18, cioe' corrispondente proprio al 17 che mancava! Questa interfaccia, IValue, non esiste piu' nel framework ma alcuni commenti su varie implementazioni del metodo GetTypeCode() sono rimasti come:

// IValue implementation

perche', probabilmente, conteneva questo metodo GetTypeCode. Questa ormai e' "archeologia" di .NET! :-) E non e' tutto. Guardate nei commenti dell'enum System.TypeCode:

// Note that when an object has a given TypeCode, there is no guarantee that
// the object is an instance of the corresponding System.XXX value class. For
// example, an object with the type code TypeCode.Int32 might actually be an
// instance of a nullable 32-bit integer type

Questo comportamento non e' piu' vero, perche' il valore di Type.GetTypeCode(typeof(int?)) e' TypeCode.Object, mentre il valore di Type.GetTypeCode(typeof(int)) e' TypeCode.Int32!

Dopo una discussione tecnica con Raf, direi che sia impossibile non cliccare "New Post"! :-)

posted @ mercoledì 12 marzo 2008 5.49 | Feedback (9) | Filed Under [ Un po' di storia Carillon .NET ]

lunedì 3 marzo 2008

Un DSL per i mapping O/R?

Guardando in questi giorni Hibernate Tools, mi e' piaciuta molto l'idea che sta alla base della la sua architettura: il mapping viene rappresentato da un'istanza del metamodello di Hibernate (classe org.hibernate.cfg.Configuration o derivate, equivalente di NHibernate.Cfg.Configuration) e da questo modello vengono generati i vari artefatti tramite cosiddetti exporter (per esempio hbm2java, hbm2ddl, hbm2hbmxml, etc), che non sono altro che classi che derivano da org.hibernate.tool.hbm2x.GenericExporter e che, insieme a dei template FreeMarker, definiscono in modo straordinariamente flessibile la parte di generazione codice/artefatti. Quindi, al posto della POJOExporter potremmo avere, perche' no, anche una POCOExporter per il domain model C#. Sarebbe interessante replicare in NHibernate un'infrastruttura simile, soprattutto adesso quando la parte di generazione codice via template T4 si trova gia' inclusa in VS2008. E poi, se tutto questo viene generato da un'istanza di Configuration, ci vuole solo un passo per creare un DSL grafico (con l'immenso aiuto di DSL Tools) che rappresenti il mapping (N)Hibernate. E un po' quello che hanno fatto in Java con, per esempio, Exadel Studio Pro, diventato adesso parte di Jboss Developer Studio. Ma poi, i concetti di mapping sono quasi independenti dagli engine e quindi, perche' non pensare a un tool visuale capace di risolvere la parte mapping per piu' ORM?

posted @ lunedì 3 marzo 2008 23.50 | Feedback (1) | Filed Under [ VSX ]

lunedì 21 gennaio 2008

L'OR Designer e' stato scritto con DSL Tools

Lo sapevate che l'Object Relational Designer e la parte di generazione di codice per le classi LINQ to SQL in VS2008 sono state scritte utilizzando Microsoft DSL Tools che fa parte di VS SDK? - l'ho scoperto tramite il Reflector mentre studiavo l'API di questo potentissimo framework che e' DSL Tools: tra le classi che derivano da Microsoft.VisualStudio.Modeling.ModelElement, classe fondamentale per la rappresentazione degli elementi di un domain model, si trovano anche le classi internal dell'OR Designer. Questo dovrebbe dare piu' fiduccia a chi inizia o valuta di estendere Visual Studio per un certo DSL utilizzando DSL Tools! - parti complesse del VS stesso iniziano ad essere sviluppate con questo framework.

posted @ lunedì 21 gennaio 2008 2.29 | Feedback (2) | Filed Under [ Carillon .NET VSX ]

martedì 11 dicembre 2007

code and solutions (and a resume)

Via questo post del mio connazionale Cosmin Negruseri, sono venuto a conoscenza di questa idea tanto bella quanto semplice ed efficace: proprio nella pagina Jobs di Facebook c'e' una sezione di programming puzzles belli tosti, provateli per credere. Ogni problema finisce coll'invito:

Please send your code and solutions (and a resume) to { (0xFACEB00C >> 2) in decimal format } @ facebook.com

Notate "and a resume" messo tra parentesi, per non dire dell'indirizzo email veramente originale ;) Complimenti!

(Oggi faccio gli 'anta ;) Non si scherza piu'!)

posted @ martedì 11 dicembre 2007 1.38 | Feedback (14) | Filed Under [ Quiz Sharp Adrian ]

giovedì 29 novembre 2007

Il pattern Decorator e la decorazione con un extension method generico

Stamattina, al corso che sto tenendo di architettura base, ho presentato ai ragazzi il classico pattern Decorator per servirci poi nell'implementare i vari servizi di validazione, logging, caching, etc., come decoratori di un repository, ispirato da questo post di Ayende. Uno di loro mi ha chiesto se si poteva scrivere il corpo della CreateComponent in modo ancora piu' usabile. E mi e' venuta l'idea di utilizzare un extension method generico fluente, tanto per introdurli un po' anche nel mondo di C# 3.0

Partiamo dall'implementazione standard del pattern:

using System;

public interface IComponent {
    void Operation();
}

public class ConcreteComponent : IComponent {
    public void Operation() {
        Console.WriteLine("ConcreteComponent");
    }
}

public abstract class Decorator : IComponent {
    private readonly IComponent component;

    protected Decorator(IComponent component) {
        if (component == null) throw new ArgumentNullException("component");
        this.component = component;
    }

    public virtual void Operation() {
        this.component.Operation();
    }
}

public class ConcreteDecoratorA : Decorator {
    public ConcreteDecoratorA(IComponent component) : base(component) { }

    public override void Operation() {
        Console.WriteLine("ConcreteDecoratorA");
        base.Operation();
    }
}

public class ConcreteDecoratorB : Decorator {
    public ConcreteDecoratorB(IComponent component) : base(component) { }

    public override void Operation() {
        Console.WriteLine("ConcreteDecoratorB");
        base.Operation();
    }
}

class Program {
    public static IComponent CreateComponent() {
        return
            new ConcreteDecoratorB(
            new ConcreteDecoratorA(
            new ConcreteComponent()));

    }

    static void Main() {
        IComponent component = CreateComponent();
        component.Operation();
    }
}

Questo snippet quindi stampa:

ConcreteDecoratorB
ConcreteDecoratorA
ConcreteComponent

a console. L'idea per modificare il metodo CreateComponent e' semplice: se definiamo una classe statica ComponentExtension contenente un'extension method generico come di seguito:

public static class ComponentExtension {
    public static Decorator DecorateWith<T>(this IComponent component) where T : Decorator {
        return (T)Activator.CreateInstance(typeof(T), component);
    }
}

il metodo CreateComponent diventa:

public static IComponent CreateComponent() {
    return new ConcreteComponent()
            .DecorateWith<ConcreteDecoratorA>()
            .DecorateWith<ConcreteDecoratorB>();

}

che, a mio parere, e' piu' espressivo. Che ne dite?

posted @ giovedì 29 novembre 2007 2.47 | Feedback (7) | Filed Under [ Pattern Dappertutto GUISA ]

domenica 18 novembre 2007

Quiz Sharp #68 [con smiley]

Questo snippet (di cui manca un pezzo) si esegue in Visual Studio premendo F5 e rimane fermo al breakpoint definito nel codice del metodo Main. Muovendo poi lentamente il mouse sulla finestra dello snippet in Visual Studio, si nota che a volte viene stampato uno smiley :-) a console.

class Quiz {
    // da completare
    static void Main() {
        System.Diagnostics.Debugger.Break(); // breakpoint
    }
}

Si chiede di completare il codice che manca.

posted @ domenica 18 novembre 2007 10.12 | Feedback (6) | Filed Under [ Quiz Sharp ]

sabato 3 novembre 2007

Quiz #67 [triple wrapping exceptions]

Senza utilizzare alcun throw, completate il seguente snippet in tal modo che il catch stampi Ciao! a console e tutte e tre le inner exception devono essere di tipi diversi:

using System;

class Quiz {
    // ... da completare

    static void Main() {
        try {
            // ... da completare
        }
        catch (Exception e) {
            if(e.InnerException != null && e.InnerException.InnerException != null && e.InnerException.InnerException.InnerException != null) {
                Console.WriteLine("Ciao!");
            }
        }
    }
}

posted @ sabato 3 novembre 2007 22.42 | Feedback (7) | Filed Under [ Quiz Sharp ]

lunedì 29 ottobre 2007

Collecting Parameter e Fluent Interface - due pattern simmetrici

Ho notato oggi delle simmetrie tra questi due pattern: il pattern di refactoring, CollectingParameter, che accumula informazioni nell'istanza del parametro (input) e il pattern FluentInterface, che accumula informazioni nel valore di ritorno (output). Il tipo del parametro e' diverso dal tipo che contiene i metodi nel CollectingParameter, mentre il tipo del valore di ritorno e identico al tipo che contiene i metodi nel FluentInterface.

Esempi di altre simmetrie nei pattern trovate nel nostro wiki, raccolti da Luca.

posted @ lunedì 29 ottobre 2007 23.52 | Feedback (0) | Filed Under [ Pattern Dappertutto ]

lunedì 8 ottobre 2007

raise accessor in C++/CLI

Due anni e mezzo fa, parlavo in questo post, di tre eventi all'interno delle classi del framework, tutti e tre nell'assembly Microsoft.VisualBasic.dll, provvisti non solo dei classici accessor add e remove, ma anche di raise (.fire in IL), accessor che non esiste ancora in C# e finivo il post chiedendomi in quale linguaggio sia stato scritto quell'assembly, Microsoft.VisualBasic.dll.

E oggi scopro che C++/CLI (ECMA-372, 19.6.2) mette a disposizione tre accessor anziche' due: add, remove e raise. Quindi, lo snippet del mio vecchio post, diventa in C++/CLI:

delegate void FooFiredEvent();

ref class Foo {
    FooFiredEvent^ m_FooFired;

    public: event FooFiredEvent^ FooFired {
        void add(FooFiredEvent^ value) {
            m_FooFired += value;
        }
        void remove(FooFiredEvent^ value) {
            m_FooFired -= value;
        }
        void raise() {
            if(m_FooFired) {
                m_FooFired();
            }
        }

    }
};

posted @ lunedì 8 ottobre 2007 21.37 | Feedback (2) | Filed Under [ Carillon .NET ]

lunedì 24 settembre 2007

Quiz Sharp #66 [junior]

Questi due snippet compilano senza errori o warning e sembrano equivalenti, ma non lo sono:

using System;

delegate Foo Foo();

class Test {
    static Foo Quiz() {
        Console.WriteLine("*");
        return Test.Quiz;
    }

    static void Main() {
        Quiz()()();
    }
}
imports System

delegate function Foo as Foo

class Test
    shared function Quiz as Foo
        Console.WriteLine("*")
        return Test.Quiz
    end function

    shared sub Main
        Quiz()()()
    end sub
end class

Cosa manca allo snippet VB.NET per essere equivalente a quello C#?

posted @ lunedì 24 settembre 2007 5.44 | Feedback (2) | Filed Under [ Quiz Sharp ]

martedì 28 agosto 2007

Quiz Sharp #65 [junior]

Una classe Foo ha un unico costruttore, pubblico e con un parametro. Puo' una classe Bar, che deriva da Foo, avere un costruttore senza parametri?

posted @ martedì 28 agosto 2007 22.54 | Feedback (8) | Filed Under [ Quiz Sharp ]

sabato 28 luglio 2007

Un modo di marcare il valore di default di un enum

"Do provide a value of zero on simple enums" dice una linea guida del FDG (p. 95). Ma forse sarebbe ancora piu' espressivo marcare questo valore di default di un enum appunto come una default value expression (ECMA-334, 14.5.14) anziche' impostarlo a 0. Cioe', scrivere:

public enum Compression
{
    None = (int)default(Compression),
    GZip,
    Deflate
}

al posto di None = 0. In ogni caso, il compilatore genera lo stesso IL. E' solo un'idea, che ne dite?

posted @ sabato 28 luglio 2007 19.12 | Feedback (4) | Filed Under [ Carillon .NET Pattern Dappertutto ]

lunedì 9 luglio 2007

Quiz Sharp #64 [junior]

Che differenza c'e' tra (new HybridDictionary(5).Values) e (new HybridDictionary(6).Values)?

posted @ lunedì 9 luglio 2007 17.35 | Feedback (3) | Filed Under [ Quiz Sharp ]

domenica 8 luglio 2007

GetCallingLanguage [la soluzione di un quiz di Claudio Brotto]

Un po' di mesi fa, Claudio Brotto, aveva proposto un simpatico quiz a cui vorrei dare adesso una soluzione (in realta' la soluzione e' per una variante leggermente modificata del quiz). Si tratta praticamente di scrivere un metodo pubblico in un assembly in tal modo che, chiamandolo da un altro assembly, questo metodo produca una stringa diversa in base al linguaggio in cui e' stato scritto l'assembly chiamante. Per esempio, questo metodo dovrebbe ritornare la stringa "C#" se chiamato da un assembly scritto in C#, oppure "Basic" se chiamato da un assembly scritto in VB.NET, etc. Piccolo vincolo della mia soluzione: l'assembly chiamante deve essere compilato in debug mode.

Tutto si basa su un metadato che il compilatore inserisce nell'assembly quando compiliamo in debug mode e che serve ai vari debugger. Cercate nel file "foo.il" ottenuto da un “foo.cs“ qualunque:

csc /debug+ foo.cs
ildasm foo.exe /linenum /out:foo.il/

la direttiva ".language" (vedi C.3, Partition VI, ECMA-335) e troverete 3 GUID che identificano rispettivamente il linguaggio, il vendor (cioe' il compilatore) e il documento. Solo il primo, cioe' il GUID del linguaggio, e' obbligatorio nella direttiva ".language". Per vedere l'elenco dei linguaggi mappati da questi GUID, andate nel file "C:\Program Files\Microsoft.NET\SDK\v2.0\include\corsym.h".

Come possiamo pero' ottenere dal codice, questa informazione? Dopo un po' di giri col Reflector, ho trovato la classe giusta, System.Diagnostics.SymbolStore.SymDocument, nell'assembly "ISymWrapper.dll" (nella cartella del framework). In questa classe notiamo la proprieta' Language di tipo Guid che ci dara' il GUID del linguaggio, incontrato sopra nel file ".il". Il costruttore di questa classe, nonostante pubblico, non ci aiuta tanto, visto il pointer bruttino che non possiamo passarglielo come argomento... Ma sempre coll'aiuto del Reflector, scopriamo chi utilizza questa interessante classe: il metodo GetDocuments della classe System.Diagnostics.SymbolStore.SymReader. E qui la stessa storia per il suo costruttore pubblico, ma indagando che altra classe utilizza la SymReader, arriviamo alla System.Diagnostics.SymbolStore.SymBinder, che finalmente ha un costruttore pubblico senza parametri!

Bene, in questa classe SymBinder, andiamo a vedere un po' il metodo GetReader con il quale potremo ottenere un'istanza di SymReader, e da qui ottenere un'istanza di SymDocument, per arrivare poi al GUID del linguaggio.

In questo metodo GetReader, incontriamo un parametro chiamato "importer", di tipo IntPtr, che punta (dice la documentazione) sulla "metadata import interface". Questa interfaccia, IMetadataImport, si trova, insieme alle altre interfacce unmanaged per i metadati, documentata nel MSDN a questa pagina probabilmente poco visitata. Il giro non e' finito perche', per ottenere un'istanza importer di IMetadataImport, abbiamo bisogno di un "dispenser" a cui chiedere di aprire il file dell'assembly (tramite il metodo OpenState) e trovare l'istanza dell'importer in un parametro out. L'istanza del "dispenser" la otteniamo conoscendo il CLSID_CorMetaDataDispenser (vedi il file "cor.h" - l'header generale del runtime se avete downloadato il SSCLI (Rotor)).

Dobbiamo definirci queste due interfacce unmanaged, decorandole con i loro GUID presi sempre dal file "cor.h", ma senza dover definire necessariamente i metodi che non ci interessano - per questo motivo ho messo dei metodi Dummy, semplicemente per occupare lo slot di un metodo che non ci interessa e per poter cosi' arrivare correttamente al metodo OpenState che e' il secondo metodo dell'interfaccia.

Quello che ho detto sopra, viene implementato cosi' (sicuramente non e' perfetto come codice ma serve solo per illustrare il concetto):

// foo.cs
// csc /t:library /r:ISymWrapper.dll foo.cs
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Diagnostics.SymbolStore;
using System.Diagnostics;

[
    Guid("7DAC8207-D3AE-4c75-9B67-92801A497D44"),
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    ComVisible(true)
]
public interface IMetadataImport
{
    void Dummy();
}

[
    Guid("809c652e-7396-11d2-9771-00a0c9b4d50c"),
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    ComVisible(true)
]
interface IMetaDataDispenser
{
    void Dummy(); // DefineScope
    // https://msdn2.microsoft.com/en-us/library/ms231248.aspx
    void OpenScope
        (
            [In, MarshalAs(UnmanagedType.LPWStr)] string szScope,
            [In] int dwOpenFlags,
            [In] ref Guid riid,
            [Out, MarshalAs(UnmanagedType.IUnknown)] out object ppIUnk
        );
}

public class Metadata
{
    public static string GetCallingLanguage()
    {
        Assembly assembly = Assembly.GetCallingAssembly();
        foreach (DebuggableAttribute attribute in assembly.GetCustomAttributes(typeof(DebuggableAttribute), false))
        {
            if (!attribute.IsJITTrackingEnabled)
            {
                throw new NotSupportedException("Calling assembly not built in Debug mode");
            }
        }
        string location = assembly.Location;

        Guid CLSID_CorMetaDataDispenser = new Guid("e5cb7a31-7512-11d2-89ce-0080c792e5d8");
        Guid IID_IMetadataImport = new Guid("7DAC8207-D3AE-4c75-9B67-92801A497D44");

        object importerObj;
        IMetaDataDispenser dispenser = (IMetaDataDispenser)Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_CorMetaDataDispenser));
        dispenser.OpenScope(location, 0, ref IID_IMetadataImport, out importerObj);
        IntPtr importer = Marshal.GetComInterfaceForObject(importerObj, typeof(IMetadataImport));
        ISymbolReader reader = new SymBinder().GetReader(importer, location, string.Empty);

        // see LanguageType module in
        // C:\Program Files\Microsoft.NET\SDK\v2.0\include\corsym.h
        Dictionary languages = new Dictionary();
        languages.Add(new Guid("3f5162f8-07c6-11d3-9053-00c04fa302a1"), "C#");
        languages.Add(new Guid("3a12d0b8-c26c-11d0-b442-00a0244a1dd2"), "Basic");
        // ...

        return languages[reader.GetDocuments()[0].Language];
    }
}

Adesso, questo codice:

// bar.cs
// csc /r:foo.dll /debug+ bar.cs
class Test
{
    static void Main()
    {
        System.Console.WriteLine(Metadata.GetCallingLanguage());
    }
}

stampera' C# a console, mentre questo:

' bar.vb
' vbc /r:foo.dll /debug+ bar.vb
Class Test
    Shared Sub Main
        System.Console.WriteLine(Metadata.GetCallingLanguage())
    End Sub
End Class

che e' identico con quello C# ma scritto in VB.NET, stampera' Basic :-)

posted @ domenica 8 luglio 2007 18.04 | Feedback (2) | Filed Under [ Quiz Sharp Carillon .NET ]

lunedì 11 giugno 2007

Quiz Sharp #63

Questo snippet:

class Test
{
    static void Main()
    {
        try
        {
            // qui manca del codice
        }
        catch{}
    }
}

a cui manca un pezzo di codice solo nel posto indicato dal commento, si esegue con un errore a runtime nonstante il catch generale. Che codice manca?

posted @ lunedì 11 giugno 2007 10.42 | Feedback (7) | Filed Under [ Quiz Sharp ]

sabato 5 maggio 2007

Quiz Sharp #62

Che parametri dovremmo passare per chiamare il secondo metodo DoSomething (quello in rosso)? Offrite una spiegazione.

using System;

class Foo
{
    public void DoSomething()
    {
        Console.WriteLine("()");
    }
    public void DoSomething(params object[] args)
    {
        Console.WriteLine("(params object[])");
    }
    public void DoSomething(object arg, params object[] args)
    {
        Console.WriteLine("(object, params object[])");
    }
}

posted @ sabato 5 maggio 2007 13.01 | Feedback (4) | Filed Under [ Quiz Sharp ]

venerdì 23 marzo 2007

Le 19 cose impossibili delle classi statiche

Per ottenere l'elenco completo delle cose che NON si possono fare con le classi statiche, e' piu' facile andare nel file "errors.h" di SSCLI 2.0 e cercare gli errori che riguardano le static class. L'elenco ottenuto e' piu' dettagliato rispetto a quello del paragrafo 17.1.1.3 delle specifiche:

  • CS0418, AbstractSealedStatic ("una classe astratta non può essere sealed o static")
  • CS0441, SealedStaticClass ("una classe non può essere contemporaneamente static e sealed")
  • CS0708, InstanceMemberInStaticClass ("impossibile dichiarare membri di istanza in una classe statica")
  • CS0709, StaticBaseClass ("una classe non può derivare da una classe statica")
  • CS0710, ConstructorInStaticClass ("le classi statiche non possono avere costruttori di istanza")
  • CS0711, DestructorInStaticClass ("le classi statiche non possono contenere distruttori")
  • CS0712, InstantiatingStaticClass ("impossibile creare un'istanza della classe statica")
  • CS0713, StaticDerivedFromNonObject ("le classi statiche devono derivare da System.Object")
  • CS0714, StaticClassInterfaceImpl ("le classi statiche non possono implementare interfacce")
  • CS0715, OperatorInStaticClass ("le classi statiche non possono contenere operatori definiti dall'utente")
  • CS0716, ConvertToStaticClass ("impossibile convertire in un tipo statico")
  • CS0717, ConstraintIsStaticClass ("impossibile utilizzare le classi statiche come vincoli")
  • CS0718, GenericArgIsStaticClass ("impossibile utilizzare tipi statici come argomenti generici")
  • CS0719, ArrayOfStaticClass ("gli elementi di una matrice non possono essere di tipo statico")
  • CS0720, IndexerInStaticClass ("impossibile dichiarare indicizzatori in una classe statica")
  • CS0721, ParameterIsStaticClass ("impossibile utilizzare tipi statici come parametri")
  • CS0722, ReturnTypeIsStaticClass ("impossibile utilizzare tipi statici come tipi restituiti")
  • CS0723, VarDeclIsStaticClass ("impossibile dichiarare una variabile di un tipo statico")
  • CS1057, ProtectedInStatic ("le classi statiche non possono contenere membri protected o protected internal")

Magari a qualcuno serve come riferimento.

posted @ venerdì 23 marzo 2007 13.43 | Feedback (1) | Filed Under [ Carillon .NET ]

Powered by: