Carillon .NET
Ho scoperto una cosa a mio parere strana: un metodo (System.Web.UI.WebControls.ParameterCollection.Add(String, DbType, String)) che esiste solo nelle versioni service pack del framework .NET (2.0 SP2, 3.0 SP2, 3.5 SP1) ma non nelle versioni "normali". Mi chiedo come mai se il metodo e' stato introdotto in .NET 2.0 SP2, l'abbiano tolto dalle .NET 3.0 e .NET 3.0 SP1 per reintrodurlo nella .NET 3.0 SP2 per poi toglierlo di nuovo dalla .NET 3.5 e finalmente reintrodurlo nella .NET 3.5 SP1???...
[OT] Per i tanti amici milanesi: sono a Milano dall'8 di marzo e torno in Romania settimana prossima, probabilmente mercoledi' - se vi fa...
Nel suo post di ieri, "foreach l'insidioso", Luca si e' chiesto come mai il seguente snippet:
using System.Collections.Generic;
interface IPersistent { }
class Invoice : IPersistent { }
class Order : IPersistent { }
class Program {
static void Main() {
List<IPersistent> changedDocuments = new List<IPersistent>();
changedDocuments.Add(new Invoice());
changedDocuments.Add(new Order());
foreach (Invoice changedInvoice in changedDocuments) { }
}
}
compili. Secondo me, il comportamento del compilatore e' giusto, voluto e documentato. Le specifiche del linguaggio (15.8.4, ECMA-334), dicono:
"A foreach statement of the form
foreach(V v in x) embedded-statement
is then expanded to:
{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}
The variable e is not visible to or accessible to the...
Il comportamento del compilatore C#, presentato prima qui da Diego e poi nel mio post precedente, sembra singolare tra gli altri compilatori piu' conosciuti .NET. Il seguente snippet C# entra in stack overflow:
using System;
class Foo {
public virtual void Write(string s) {
Console.WriteLine("Foo virtual " + s);
}
}
class Bar : Foo {
public override void Write(string s) {
Console.WriteLine("Bar override " + s);
}
public void Write(string s, params string[] args) {
Write("Bar overload " + s);
}
}
class Program {
static void Main() {
Bar bar = new Bar();
bar.Write("Ciao!"); // Process is terminated due to StackOverflowException
}
}
mentre per gli altri linguaggi, stampa Bar override Ciao! Di seguito il codice equivalente in Visual...
Questo post di Diego Martelli, fattomi notare da un amico, riesce secondo me a sorprendere un comportamento interessante di C#, ovvero il seguente snippet di codice entra in stack overflow:
using System;
class Foo {
public virtual void Write(string s) {
Console.WriteLine("Foo virtual " + s);
}
}
class Bar : Foo {
public override void Write(string s) {
Console.WriteLine("Bar override " + s);
}
public void Write(string s, params string[] args) {
Write("Bar overload " + s);
}
}
class Program {
static void Main() {
Bar bar = new Bar();
bar.Write("Ciao!");
}
}
Probabilmente molti si aspetterebbero che venisse stampato "Bar override Ciao!" a console e invece il metodo chiamato e' il Write con l'elenco variabile di parametri...
Oggi, parlando con il mio collega Daniel, e' uscita fuori una situazione interessante. Supponiamo che avete un tool che vi genera una classe partial che implementa IDisposable:
using System;
partial class Foo : IDisposable {
public void Dispose() {
Console.WriteLine("implicit");
}
}
Se volete cambiare il comportamento del Dispose senza toccare il codice generato sopra, basta implementare esplicitamente l'interfaccia IDisposable:
using System;
partial class Foo {
void IDisposable.Dispose() {
Console.WriteLine("explicit");
}
}
A questo punto uno using(Foo foo = new Foo()){} stampera'...
Magari lo sanno tutti ma io ci ho perso mezz'ora per capire dove sta l'errore:
<script type="text/javascript" src="foo.js"></script>
e
<script type="text/javascript" src="foo.js" />
non sono uguali! Su IE il tag esplicito di chiusura (la prima variante) e' obbligatorio, mentre Firefox accetta tutte e due le varianti...
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...
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...
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...
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...
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...
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...
"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?
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...
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...
La soluzione al Quiz Sharp #61 mi sembra abbastanza interessante per meritare un post a parte (nomi come Miguel de Icaza, Jon Skeet, Abhinaba Basu, hanno considerato strano il comportamento del compilatore C# 2.0 nel comparare i valori value type a null)
La risposta corretta al quiz e' che eseguendo lo snippet si ottiene un'eccezione a runtime (StackOverflowException). Vediamo di seguito il perche', con le specifiche in mano:
Per evaluare l'espressione value != null nell'implementazione dell'operatore, dove value e' un not-nullable value type, il compilatore applica alcune conversioni. Per esempio, per ogni nullable type, esiste una conversione implicita dal tipo null al...
Via questo post di Kevin Williams, scopro che PHP, a partire dall'ultima versione (5.2.0), supporta "constructors in interfaces to force constructor signature checks in implementations". Si potrebbe quindi scrivere:
<?phpinterface IFoo { public function __construct ();}class Foo implements IFoo { public function __construct () { echo "Ciao!\n"; }}?>
Senza il costruttore della classe Foo, avremmo ottenuto l'errore: "Class Foo contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (IFoo::__construct)".
Come avevo gia' scritto in questo vecchio post, il CLR non consente costruttori di istanza nelle interfacce (costruttori static invece, si').
Questo post di David Hayden, segnalato da Michele, che riprende un paragrafo da questo libro (non l'ho ancora visto nelle librerie ma un altro libro di Christian Gross mi è piaciuto molto) mi ha incuriosito perché sia in C# (CS0451) che in VB.NET (BC32103) è impossibile avere insieme una constraint struct/Structure e una constraint new()/New. E quindi mi sono chiesto come mai "without the class constraint, the compiler doesn't know if the type T is a value type or reference type and hence has to check for both"? Se struct e new() non possono stare insieme non significa che il...
A volte, un umile metodo come l'Equals statico della classe System.Object può rendersi sorprendentemente utile. Per esempio, in una classe generica in cui il type parameter T può essere sia una class che una struct, abbiamo la necessità di controllare che un'istanza di T sia default(T).
class Foo<T> { public bool EqualsDefault(T t) { /* ? */ } }
Senza l'Equals statico le soluzioni sono abbastanza bruttine:
non possiamo avere t == null perché T può essere una struct;
non possiamo avere t == default(T) per l'errore CS0019 ("Operator '==' cannot be applied to operands of type 'T' and 'T'");
non possiamo...
Grazie a Corrado, ieri ho ricevuto questo commento da Paul Vick al post "Overloading non-generic virtual methods in generic classes (C# vs VB.NET)":
"Corrado Cavalli pointed me to your question -- the answer is that VB and C# differ in their overload resolution rules. C# only looks at one level in the hierarchy at a time, while VB looks at all the members in the inheritance chain at the same time. So C# only sees Bar::DoSomething(T), while VB sees Bar::DoSomething(T) and Bar::DoSomething(Object). We prefer less generic methods over more generic methods, so you get the Object overload"
Thank you Paul!
Ho appena creato un thread sul forum di GUISA (Gruppo Utenti Italiani Solution Architect) in cui offro un esempio dove ha senso che il metodo GetHashCode ritorni sempre un valore costante e dove il metodo Equals è molto atipico: due istanze sono uguali se sono istanze del tipo contenente questi metodi - quindi semantica statica per una classe non-statica.
Cercherò di postare lì le cose che riguardano l'architettura, per seguire sia io che voi più facilmente i feedback, mentre qui sul blog solo una piccola info.
Ho notato un comportamento diverso (C# vs VB.NET) per l'overloading di metodi virtuali non-generici in classi generiche e, come notava il buon Raffaele in una discussione sul messenger "la regola di scegliere l'una o l'altra non è dettata dal CLS e qui scoppiano i problemi quando traduci un listato - un bel pasticcio". Lo snippet C# sotto:
using System; class Foo { public virtual void DoSomething(object o) { Console.WriteLine("void Foo::DoSomething(object)"); }} class Bar<T> : Foo { public void DoSomething(T t) { Console.WriteLine("void Bar<T>::DoSomething(T)"); } public override void DoSomething(object o) { Console.WriteLine("void Bar<T>::DoSomething(object)"); }} class Baz : Foo { public override void DoSomething(object o) { ...
In "verticale":
Il metodo del Framework con il corpo più lungo (sia per la versione 1.1 che per la 2.0) è il metodo pubblico void Go() della classe internal System.Web.RegularExpressions.TagRegexRunner1 che si trova nell'assembly System.Web.RegularExpressions.dll; il suo corpo in IL ha 5770 bytes, che corrispondono a 862 righe di codice C# in Reflector! - posso dire pazzesco?...
Se invece consideriamo solo i metodi pubblici dei tipi pubblici, abbiamo:
per la versione 2.0, il metodo pubblico void Register() della classe pubblica System.Globalization.CultureAndRegionInfoBuilder che si trova nell'assembly sysglobl.dll; il suo corpo in IL ha 1088 bytes, che corrispondono a 69 lunghe righe di codice C#...
Il messaggio dell'errore CS0310 (e anche la sua descrizione) secondo me è incompleto, cioè non basta che un tipo abbia un costruttore pubblico senza parametri per poter essere utilizzato come type parameter in un tipo generico con una constructor constraint. Il messaggio dell'errore dice: "The type 'typename' must have a public parameterless constructor in order to use it as parameter 'parameter' in the generic type or method 'generic'".
Controesempio:
abstract class Foo{ public Foo() { }} class Bar<T> where T : new() { }
Il tipo Foo ha un costruttore pubblico senza parametri ma l'espressione new Bar<Foo>() non compila (error CS0310).
La frase nelle...
Qualcuno (diciamo alle prime armi con .NET) si potrebbe chiedere dove sta
l'implementazione di un evento di un'interfaccia, vista la sintassi C#:
interface
IFoo
{
event
EventHandler Bar;
}
class
Foo : IFoo
{
public
event
EventHandler
Bar;
}
Sembra che la classe Foo non implementi un bel nulla, e invece, con
questa sintassi, noi in realtà accettiamo l'implementazione di default
dei metodi add_Bar e remove_Bar, che la scriverà per noi il
compilatore (vedi il codice IL corrispondente). Meno confusione si crea
quando scegliamo di definire esplicitamente l'evento nella classe Foo;
lì i metodi delle funzioni di accesso add e remove tolgono ogni
dubbio sull'implementazione dell'evento.
A volte, il codice "riflesso" con il Reflector, ci mette su false piste. Qualcuno, guardando per esempio l'implementazione dei singleton delle classi factory dei provider ADO.NET, tramite il Reflector, potrebbe erroneamente pensare che inizializzare esplicitamente un campo statico nel costruttore statico fosse una best practice:
// snippet 1// codice tramite il Reflectornamespace System.Data.SqlClient{ public sealed class SqlClientFactory : DbProviderFactory { public static readonly SqlClientFactory Instance; // codice non ottimizzato static SqlClientFactory() { Instance = new SqlClientFactory(); } private SqlClientFactory() { } // ... }}
E invece, il codice "reale" è questo:
// snippet 2namespace System.Data.SqlClient{ public sealed class SqlClientFactory : DbProviderFactory { ...
Quasi da non credere (J. Duffy, "Professional .NET Framework 2.0", p. 61):
"The actual IL emmited shows some of the complexities of delegates in the underlying type system:"
struct MyDelegate : System.MulticastDelegate{ //[...]}
"[...]
Notice first that the MyDelegate type breaks one of the rules discussed above, namely that structs cannot derive from types other than ValueType. Delegates have special support in the CTS, so this is allowed."
Abbastanza sconvolgente questo paragrafo, non trovate?
Certi linguaggi, come per esempio J#, impongono l'accesso public al metodo entrypoint. Però, dal punto di vista del utilizzatore della classe che contine l'entrypoint, questo membro public non presenta alcun interesse, cioè non viene quasi mai richiamato dal codice. Sto pensando quindi che si potrebbe decorare con l'attributo EditorBrowsable(EditorBrowsableState.Never) in tal modo da non comparire nell'elenco intellisense dei membri pubblici della classe.
Ogni compilatore è libero di ordinare l'elenco di attributi multi-use che decorano un elemento di codice in base alle sue proprie regole. In questo senso, le specifiche di C# avvertono: "The order in which attributes are specified in an attribute section, and the order in which sections attached to the same program entity are arranged, is not significant" (ECMA-334, 24.2).
I seguenti 3 snippet equivalenti, scritti rispettivamente in C#, VB e J#:
using System; [Foo(1), Foo(2), Foo(3), Foo(4), Foo(5), Foo(6), Foo(7), Foo(8), Foo(9), Foo(10)][AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]class FooAttribute : Attribute{ public FooAttribute(int index) { _index = index; } private int _index; public int...
Per chi vuole scriversi un compilatore per il CLR, sicuramente un grande aiuto troverà nella Microsoft Common Compiler Infrastructure (CCI). Questo framework non è direttamente scaricabile ma arriva (per esempio) insieme al compilatore Zonnon o all'utilissimo FxCop.
Purtroppo, le versioni della CCI che troverete in Zonnon e FxCop sono diverse:
Zonnon:
System.Compiler.dll (Compiler oriented replacement for System.Reflection and System.Reflection.Emit)
System.Compiler.Framework.dll (Contains a collection of standard compiler base classes as well as visitors for the standard node types defined in System.Compiler)
System.Compiler.Runtime.dll (Extensions to the Common Language Runtime used by the Common Compiler Infrastructure)FxCop
Microsoft.Cci.dll (Incorpora alcune classi di Fugue, senza però utilizzare gli assembly...
Parlando con Massimo oggi, ho notato delle simmetrie/asimmetrie nelle scelte di quelli che hanno progettato C# e VB:
Nel caso di VB, la visibilità implicita (cioè pubblica) dell'implementazione di un'interfaccia definisce un'implementazione implicita dell'interfaccia. Stessa parola ("implicita") ma due sensi diversi.
Nel caso di C#, la visibilità esplicita dell'implementazione di un'interfaccia non è consentita per definire un'implementazione esplicita. Stessa parola ("esplicita") ma due sensi diversi anche in questo caso.
Lo sapevate che esistono classi nel Framework .NET che portano lo stesso nome (ovviamente in namespace differenti)?
[mscorlib]System.Runtime.Remoting.Contexts.SynchronizationAttribute - [System.EnterpriseServices]System.EnterpriseServices.SynchronizationAttribute
Altri esempi? (l'esempio di sopra l'ho trovato nel recente libro di Nagel, p. 34)
(Aggiornamento 20/11/2005): Nei commenti trovate l'elenco completo dei 60 esempi di classi pubbliche nella 2.0 (Beta 2) che portano lo stesso nome e che hanno come root namespace, System (altrimenti sarebbero stati molti di più).
Facendo oggi con Massimo il refactoring con generics della classe DotNetNuke.Common.Utilities.Null, sono nate alcune considerazioni sui tipi generici. Di seguito il post a 4 mani - si nota anche dall'italiano :-)
Avendo una classe static Foo che espone funzionalità generiche e più conveniente definire il type parameter per un metodo (Dummy) piuttosto che per la classe. Questo perché a runtime verrà generata un'unica classe Foo con più metodi Dummy anziché più classi Foo con un metodo Dummy ciascuna. Per esempio, il seguente snippet:using System; class Foo<T>{ static Foo() { Console.WriteLine("Foo<{0}>", typeof(T)); } public static void Dummy() { }} class Foo{ static Foo() { ...
Se in C# questo snippet:
using System;using System.Reflection; class Foo{ static void Main() { Console.WriteLine(Assembly.GetEntryAssembly().EntryPoint.Name); }}
stampa Main a console, non la stessa cosa si può dire dell'equivalente C++:
using namespace System;using namespace System::Reflection; void main(){ Console::WriteLine(Assembly::GetEntryAssembly()->EntryPoint->Name);}
che stampa a console:
main
mainCRTStartup oppure
_mainCRTStartup
in base alle opzioni:
/clr:safe
/clr:pure rispettivamente
/clr:oldSyntax (oppure /clr:initialAppDomain)
dove mainCRTStartup e _mainCRTStartup sono funzioni di C/C++ run-time startup. Il compilatore (tranne nel caso /clr:safe) chiama prima queste funzioni per le inizializzazioni necessarie alla C/C++ run-time library (variabili globali, heap, etc) e quindi saranno loro quelle decorate con .entrypoint in IL e non il metodo main dello snippet. L'opzione /clr:safe invece, produce codice verificabile e non...
Se avete bisogno di indviduare il corrispondente IL di un frammento di metodo, basta creare un block in C# per il rispettivo frammento di codice (che sarà delimitato da due nop in IL):
<method signature in C#>{ // codice... { // // zona di interesse // } // codice...}
<method signature in IL>{ // codice... nop // // zona di interesse // nop // codice...}
Appena ho visto la nuova XslCompiledTransform (in 2.0) è stato colpo di fulmine. E da questo esempietto fatto al volo, solo per rendere l'idea, capirete subito perché:
using System;using System.Text;using System.IO;using System.Xml;using System.Xml.Xsl; class Test{ static void Main() { string methodName = "Foo"; StringBuilder methodCode = new StringBuilder(); methodCode.AppendLine ("// ritorna la stringa Ciao ragazzi!"); methodCode.AppendFormat("public string {0}()", methodName); methodCode.AppendLine ("{"); methodCode.AppendLine (" StringBuilder sb = new StringBuilder();"); methodCode.AppendLine (" sb.Append(\"Ciao \");"); methodCode.AppendLine (" sb.Append(\"ragazzi!\");"); methodCode.AppendLine (" return sb.ToString();"); methodCode.AppendLine ("}"); Console.WriteLine(ExecuteMethodCode(methodCode.ToString(), methodName)); } static string ExecuteMethodCode(string methodCode, string methodName) { StringBuilder xsl = new StringBuilder(); StringBuilder result = new StringBuilder(); string...
Secondo voi, se questo snippet compila:
class Test{ static void Foo(bool b1, bool b2) { System.Console.WriteLine("{0}, {1}", b1, b2); } static void Main() { bool b1 = expr1; bool b2 = expr2; Foo(b1, b2); }}
e lasciando identiche le espressioni expr1 e expr2, dovrebbe per forza compilare senza errori anche quest'altro snippet?:
class Test{ static void Foo(bool b1, bool b2) { System.Console.WriteLine("{0}, {1}", b1, b2); } static void Main() { Foo(expr1, expr2); }}
Non sempre! Ed eccone un esempio, suggerito dalle specifiche (ECMA-334, 3rd Ed., 9.2.3 "Grammar ambiguities" - dove troverete anche altre situazioni!):
// compila senza errori e stampa True, Falseclass Test{ static void Foo(bool...
Per vedere "the actual bytes (in hex) as instruction comments" nel codice IL, abbiamo l'opzione /bytes dell'ildasm. Per esempio, per il metodo Foo::Sum della classe:
class Foo{ public int Sum(int a, int b) { return a + b; }}
otteniamo:
IL_0000: /* 00 | */ nopIL_0001: /* 03 | */ ldarg.1IL_0002: /* 04 | */ ldarg.2IL_0003: /* 58 | */ addIL_0004: /* 0A | */ stloc.0IL_0005: /* 2B | 00 */ br.s IL_0007IL_0007: /* 06 | */ ldloc.0IL_0008: /* 2A | */ ret
dove con rosso sono scritti in esadecimale gli opcode (e i loro parametri! - vedi lo 00 nella colonna...
Mi stavo chiedendo perché dobbiamo specificare esplicitamente il modificatore static per tutti i membri di una static class. Per esempio, tutti i membri di un'interfaccia, hanno implicitamente l'accesso public ed è vietato specificare qualunque modificatore. Cerco di darmi una risposta, ma è solo una mia supposizione:
Visto che possiamo dichiarare una static class così:
partial class Foo{ //...} static partial class Foo{ //...}
senza essere obbligati a specificare static per tutte le dichiarazioni parziali:
static partial class Foo{ //...} static partial class Foo{ //...}
la dichiarazione implicita static per i membri creerebbe confusione nella dichiarazione parziale:
partial class Foo{ // membri static o instance?}
visto che potrebbe...
Mi sono chiesto: perché il valore di default dell'enum System.Runtime.InteropServices.LayoutKind è LayoutKind.Sequential e non LayoutKind.Auto? (come dicono le specifiche ECMA-335, Partition II, 10.1.2, 3rd ed.).Secondo me, la risposta sarebbe questa: il layout di default è auto per i tipi di riferimento e sequential per i tipi di valore mentre l'unica signature in cui appare l'enum LayoutKind è quella del costruttore dell'attributo pseudocustom System.Runtime.InteropServices.StructLayoutAttribute che, anche se applicabile sia alle classi che alle struct, viene quasi sempre utilizzato per le struct (da dove anche il nome, StructLayoutAttribute).
Corrado segnala in questo post, lo strano uso che il compilatore VB .NET fa del metodo RuntimeHelpers.GetObjectValue.
Anche una semplice assegnazione:
Dim foo As Object = New Object
viene vista come:
object foo = System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue(new object());
In realtà, non è così strano. Leggendo i commenti nel codice Rotor per il metodo GetObjectValue, si capisce un po' di più la ragione:
"GetObjectValue is intended to allow value classes to be manipulated as Object but have aliasing behavior of a value class. The intent is that you would use this function just before an assignment to a variable of type Object. If the value being assigned is a...
Nel caso di New in VB .NET, c'è una differenza evidente però magari
poco conosciuta tra la variante escaped (sinistra, metodo) e la variante
non-escaped (destra, costruttore):
Class
Foo
Sub [New]()
End Sub
End
Class
Class
Foo
Sub
New()
End Sub
End
Class
...
Non mi aspettavo di trovare proprio 4 generazioni di discendenti di System.Attribute. Un esempio? L'attributo System.Data.SqlClient.SqlClientPermissionAttribute (e i suoi fratellini).
Se l'attributo System.ObsoleteAttribute avesse il target anche su AttributeTargets.Assembly, si eviterebbe di decorare tutti i tipi di un assembly interamente obsoleto come Obsolete.
Per esempio, nella Beta 2, tutti i tipi dell'assembly Microsoft.VisualC.dll sono decorati con [Obsolete(”Microsoft.VisualC.dll is an obsolete assembly and exists only for backwards compatibility.”)]. Potremmo cosi' avere, piu' semplice, una volta sola per l'intero assembly:
[assembly:Obsolete(”Microsoft.VisualC.dll is an obsolete assembly and exists only for backwards compatibility.”)]
Mi sono ricordato di questa domanda di Giancarlo:
"Perchè nella definizione di una interfaccia non c'è la possibilità di specificare anche una firma per eventuali costruttori?"
incontrando nel framework (Beta 2) l'attributo System.Web.UI.ConstructorNeedsTagAttribute. Praticamente, decorando con [ConstructorNeedsTag(true)] una classe, "garantiamo" al client della classe il fatto che essa abbia un costruttore con un parametro di tipo string.
E' proprio vero il fatto che l'AOP permette di estendere un linguaggio con nuove costruzioni...
Sperimentando in questi giorni delle cose, mi sono fermato su una prova che mi è apparsa piuttosto strana: il seguente snippet
using System; class Foo{ static int i = 0; ~Foo() { Foo f = new Foo(); Console.WriteLine(++i); } static void Main() { Foo foo = new Foo(); }}
stampa a console numeri consecutivi e si ferma senza errore dopo qualche decina di migliaia!...
La spiegazione l'ho trovata solo adesso, dopo tante fantasie sognate, in una pagina (467) di Richter:
"Quando è in corso il regolare arresto di un processo," [...] "ciascun metodo Finalize ha circa 2 secondi di tempo per essere restituito....
Questo codice, assolutamente simmetrico, l'ho scritto apposta per essere compilato sia in C# che in J#, senza modifiche:
// foo.txtclass Test{ static int i = 0; // J# entry point // vjc foo.txt public static void main(System.String[] args) { i++; System.Console.WriteLine(i); Main(args); } // C# entry point // csc foo.txt public static void Main(System.String[] args) { i++; System.Console.WriteLine(i); main(args); }}
Eseguendo il foo.exe, arriva la sorpresa: il numero di cicli che la variante J# riesce a fare (159830), supera il numero di cicli della variante C# (159781), fino al messaggio:
Process is terminated due to StackOverflowException.
Incuriosito da questa strana vittoria del...
A me, il fatto che il seguente snippet stampi:
TrueTrueFalse
a console, risulta stranissimo!
using System;using System.ComponentModel; class Test{ static void Main() { BindableAttribute a = BindableAttribute.Default; BindableAttribute s = new BindableAttribute(BindableSupport.Default); Console.WriteLine(a.IsDefaultAttribute()); // True Console.WriteLine(s.IsDefaultAttribute()); // True Console.WriteLine(a.Equals(s)); // False??? }}
Praticamente, l'enumeration System.ComponentModel.BindableSupport, così com'è definita nel framework:
namespace System.ComponentModel{ public enum BindableSupport { No, Yes, Default // 2??? }}
vede il valore dell'elemento Default come 2 e non come 0 (che è quello di No). In effetti, nella definizione della classe System.ComponentModel.BindableAttribute, che wrappa l'enumeration di sopra, abbiamo:
namespace System.ComponentModel{ // ... public sealed class BindableAttribute : Attribute { public static readonly BindableAttribute...
Come utilizzare un'enumeration come attribute?
Nello snippet seguente mostro un semplice ma elegante pattern per il wrapping di un'enumeration (ho inserito le spiegazioni come commenti nel codice).
Il fatto che il wrapper deriva da System.Attribute è solo per offrire una situazione concreta in cui il wrapping diventa utile e necessario. Con il valore 0 per il primo elemento dell'enumeration ho voluto ricordare una best practice spesso dimenticata: "ensure that 0 is a valid state for value types" (vedi l'item 8 del libro "Effective C#" di Bill Wagner, oppure (aggiornamento 1: 06/07/05) la regola 10.9 nel libro di Balena e Dimauro).
using...
Succede che a volte dovete cambiare i nomi degli elementi in un'enum, per esempio, da ConsoleKey.BackSpace a ConsoleKey.Backspace, oppure da ConsoleKey.SpaceBar a ConsoleKey.Spacebar, utilizzando l'attributo Obsolete. Attenzione all'ordine degli elementi con lo stesso valore! L'elemento obsolete deve essere posizionato dopo l'elemento valido dello stesso valore.
Il seguente snippet:
class Test{ static void Main() { Foo f = (Foo)1; System.Console.WriteLine(f); }}
stampa SomeThing a console se l'enum è:
// NO!public enum Foo{ [Obsolete("Use Foo.Something.")] SomeThing = 1, Something = 1}
e Something se l'enum è:
// OK!public enum Foo{ Something = 1 [Obsolete("Use Foo.Something.")] SomeThing = 1,}
Full Carillon .NET Archive