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,}
Se il seguente snippet:
delegate void FooFiredEvent(); class Foo{ public event FooFiredEvent FooFired; public void RaiseFooFired() { if (FooFired != null) { FooFired(); } }}
lo compiliamo:
csc foo.cs
poi lo disassembliamo :
ildasm foo.exe /out=bar.il
e nel file disassemblato, bar.il, inseriamo la riga in rosso:
.event FooFiredEvent FooFired{ .addon instance void Foo::add_FooFired(class FooFiredEvent) .removeon instance void Foo::remove_FooFired(class FooFiredEvent) .fire instance void Foo::RaiseFooFired()}
dopodiché lo riassembliamo:
ilasm /exe bar.il
e apriamo il file bar.exe col Reflector impostato per C#, avremo una sorpresa: nel codice disassemblato dell'evento FooFired, è apparsa una sezione, raise, che non c'è ancora nel linguaggio C# :-)
public event FooFiredEvent FooFired{ [MethodImpl(MethodImplOptions.Synchronized)] add { this.FooFired...
Un davvero splendido corso di "Managed Computation" tenuto nella primavera dell'anno scorso al Politecnico federale di Zurigo da Prof. Robert Stärk per gli studenti iscritti al programma di MSc in Computer Science, è scaricabile da questa pagina.
Nelle più di 500 slide del corso, le specifiche di C# vengono modellate come macchine astratte (ASM). Una gioia leggerlo, abbiate solo pazienza!
Si sa che i metodi corrispondenti agli event accessor devono seguire un pattern di denominazione (vedi CLS Rule 33 e ECMA-335, Partition I, 10.4). Per esempio, l'accessore add deve seguire il pattern:
void add_<EventName> (<DelegateType> handler)
Luca, in questo interessante post, è arrivato ad aver bisogno di richiamare questi event accessor. Provando col compilatore C#, si ottiene l'errore CS0571 ("cannot explicitly call operator or accessor"). Vediamo se, per il seguente snippet, il compilatore C# genera per i metodi add_FooFired e AddFooFired lo stesso codice IL:
class Foo{ private FooFiredEvent mFooFired; public event FooFiredEvent FooFired { add { mFooFired += value; } remove { ...
Non sempre quando vi appare una finestra "Just-In-Time Debugging", nel messaggio "An exception 'MyNamespace.MyException' has occurred in MyApplication.exe", l'eccezione debba essere una exception. Può capitare di incontrare al posto di 'MyNamespace.MyException', 'Launch for user' :-)
Un esempio, in questo snippet:
class Foo{ static void Main() { System.Diagnostics.Debugger.Break(); }}
Secondo me, crea un po' di confusione il messaggio.
Ve ne siete probabilmente accorti di questo commento nei vostri file IL disassemblati:
// --- The following custom attribute is added automatically, do not uncomment -------// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(bool, bool) = ( 01 00 00 01 00 00 )
Per farlo sparire basta compilare con le opzioni /debug- ("do not emit debugging information") e /o+ ("enable optimizations").
Interessante è il fatto che se non compilate con queste opzioni un assembly decorato con:
[assembly: System.Diagnostics.Debuggable(false, false)]
cioè i parametri isJITTrackingEnabled e isJITOptimizerDisabled a false, ottenete in C# l'errore CS0647:
error CS0647: Error emitting 'System.Diagnostics.DebuggableAttribute' attribute-- 'Assembly custom attribute 'System.Diagnostics.DebuggableAttribute' was specified multiple times with...
In dasm.cpp, che fa parte dell'implementazione dell'ildasm del Rotor, potete trovare questa simpatica label:
if(g_pPELoader) goto DumpTheSucker;
Beh, dopo il punto e virgola dello statement, aggiungerei un trattino e una parentesi chiusa ;-)
Pensavate che non si possa scrivere una classe in C# che non derivi da nessun'altra classe, vero? :-) Provate a compilare questo codice:
namespace System{ class Object {}}
Passando l'assembly all'ildasm, notiamo due cose "strane":
.namespace System{ .class private auto ansi beforefieldinit Object { .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { .maxstack 0 br.s here here: ret } }}
è sparita la clausa extends [mscorlib]System.Object (dobbiamo prendere sul serio il commento "note that class flags, 'extends' and 'implements' clauses are provided here for information only"? - vedi anche questo altro mio post);
il costruttore non chiama il costruttore della...
Dopo una registrazione gratuita è possibile scaricare questo interessantissimo libro (311 pagine), appena uscito:
G. Nutt, "Distributed virtual machines: inside the Rotor CLI", Pearson Addison Wesley (2005)
(Aggiornamento 17/04/05): Scopro adesso che Marco Russo l'aveva già segnalato quasi un anno e mezzo fa, quando il libro era ancora "in fase di scrittura".
E' interessante notare che il compilatore Visual C# genera per gli snippet a sinistra lo stesso codice IL degli snippet a destra:
class Foo{ static void DoSomething() { if (true) {} }}
class Foo{ static void DoSomething() {}}
class Foo{ static void DoSomething() { if (false) {} }}
class Foo{ static void DoSomething() { goto here; here:; }}
Quando l'espressione booleana è sempre true, il compilatore sostituisce l'if con il ramo then, mentre quando l'espressione booleana è sempre false, il compilatore sostituisce l'if con un salto incondizionato al ramo else. Nel primo caso si genera solo il codice per il ramo then, mentre nel secondo...
In SLAR alla pagina 389 c'è scritto che:
"This class is used only by the system; applications cannot create instances of the System.Security.SecurityElement type"
Questa affermazione non è vera: la classe SecurityElement è in sostanza un parser XML leggero (sa parsare solo elementi, attributi e testo) e può essere utilizzata come qualunque altra classe. Un bellissimo esempio è mostrato in questo post di Shawn Farkas sulla trasformazione SecurityElement <-> XmlElement. Lo dimostra anche l'esempio che si trova in SLAR nella stessa pagina (la classe Samples.SecurityElementSample).
class Foo{ static Foo() { System.Console.WriteLine("Ciao!"); } static void Main(){}}
Questo innocente snippet stampa a console Ciao! pur avendo un Main vuoto. Per evitare stranezze di questo tipo, ho tirato fuori una best practice per i Main: evitare di avere un costruttore di tipo nella classe che contiene l'entry point.
Visto che non ricevo tutti i giorni una mail da Paul Vick, la posto :-)
Adrian,
Thank you so much for your email! You are correct that the examples are wrong... it looks like they somehow slipped through our editing process!I'll make sure that they are corrected in the next version of the specification, which should be coming out with Beta2 of VB 2005.
Thanks so much for the bug reports!
Paul
La mail conferma i bug segnalati in un post precedente. Gli indirizzi MSDN degli esempi errati sono:
9.2.6 Event Handling
9.6.2 WithEvents Variables
10.5.1 RaiseEvent Statement
Dopo il post di ieri sulla classe PrivateImplementationDetails e sugli array initializer in C#, ho continuato a giocare un po' e guardate cosa ho combinato...
using System;using System.Reflection;using System.Runtime.InteropServices;class Foo{ static void ContainsLocalArrayInitializer() { int[] a = new int[3]{1, 2, 3}; } static int[] GetLocalArrayInitializerOfSomebodyElse() { int[] a = new int[3]; IntPtr buffer = Marshal.AllocHGlobal(12); Marshal.StructureToPtr(Assembly.GetExecutingAssembly().GetModules()[0]. GetType("<PrivateImplementationDetails>").GetField("$$method0x6000001-1", BindingFlags.NonPublic | BindingFlags.Static). GetValue(null), buffer, true); Marshal.Copy(buffer, a, 0, a.Length); Marshal.FreeHGlobal(buffer); return a; } static void Main() { int[] stolenArray = GetLocalArrayInitializerOfSomebodyElse(); foreach(int element in stolenArray) { Console.WriteLine(element); } }}
Lo snippet stampa a console gli elementi con cui è stato inizializzato l'array locale di...
Nell'ultimo quiz (#47) è stato l'array initializer:
int[] a = new int[3]{1, 2, 3};
a generare la classe '<PrivateImplementationDetails>' di cui proverò a parlere in questo post.
Cioè, cambiando l'array initializer con:
int[] a = new int[3];a[0] = 1;a[1] = 2;a[2] = 3;
lo snippet del quiz avrebbe stampato a console abc (la risposta B e non C).
Nel nostro caso, il codice IL generato per questa classe non documentata è:
.class private auto ansi '<PrivateImplementationDetails>' extends [mscorlib]System.Object{ .class explicit ansi sealed nested private '$$struct0x6000001-1' extends [mscorlib]System.ValueType { .pack 1 .size 12 } .field static assembly valuetype '<PrivateImplementationDetails>'/'$$struct0x6000001-1' '$$method0x6000001-1' at D_00002050}.data D_00002050 = bytearray (01...
Il seguente snippet è preso dal paragrafo 9.6.2 delle specifiche VB .NET:
Imports SystemClass Raiser Public Event Constructed() Public Sub New() RaiseEvent Constructed() End SubEnd ClassModule Test Private WithEvents x As Raiser Private Sub HandleConstructed() Handles x.Constructed Console.WriteLine("Constructed") End Sub Public Sub Main() x = New Raiser End SubEnd Module
Non mi spiego come un errore così elementare (vedi la Reference: "Non-shared events should not be raised within the constructor of the class in which they are declared. Although such events do not cause runtime errors, they may fail to be caught by associated event handlers") possa essere fatto proprio da...
In Visual Basic .NET, la keyword GetType è più vicina alla BCL rispetto alla keyword corrispondente typeof in C#. Questa vicinanza però, non deve creare confusione tra keyword e metodo. Mi spiego con un esempio tipo quiz :-)
Supponiamo di avere questi due file in Visual Basic .NET, Foo.vb:
' Foo.vbPublic Class Foo : End Class
e Test.vb:
' Test.vbImports SystemClass Test Shared Sub Main() Console.WriteLine(Type.GetType(GetType(Foo).FullName)) End SubEnd Class
che compilano senza errori. Il risultato dell'esecuzione di Test però, dipende dalle "compiler option" con cui li abbiamo compilati:
se compiliamo con:
vbc test.vb foo.vboppure con:vbc /t:module foo.vbvbc /addmodule:foo.netmodule test.vbl'esecuzione di Test stampa Foo a console;
se...
Avere in un linguaggio .NET un metodo pubblico, non statico, conforme al CLS e di una classe pubblica, in un assembly, non necessariamente vuol dire che possa essere richiamato da un altro assembly!
L'esempio non è evidente: il metodo toString in questo snippet J#:
class A extends java.lang.Object{ public String toString() { return "A::toString"; }}
non può essere richiamato da C#:
class Test{ static void Main() { // error CS0117: 'A' does not contain a definition for 'toString' System.Console.WriteLine(new A().toString()); }}
se non lo sostituiamo con ToString. Da VisualBasic .NET invece, va ("identifiers are case insensitive"):
Class Test Shared Sub Main() ' OK ...
Quasi sempre, come motivo per preferire le proprietà pubbliche ai campi pubblici di istanza, si presenta quello della possibilità di validazione dei valori nella classe corrente o nelle classi derivate.
Ne ho trovato invece un altro, molto interessante, nel libro di Lowy (p. 378, sottolineatura mia):
"Neither programmatic nor declarative security can protect against untrusted code accessing public fields because no stackcalls are involved. Never provide public fields, and always use properties".
Mi sembra contraddittorio avere per una classe sealed una security action InheritanceDemand...
Succede così per la classe Executor:
namespace System.CodeDom.Compiler{ [PermissionSet(SecurityAction.LinkDemand, Name="FullTrust")] [PermissionSet(SecurityAction.InheritanceDemand, Name="FullTrust")] public sealed class Executor { // }}
La prova:
PermissionSetAttribute è uno pseudo-custom attribute, perciò troviamo InheritanceDemand come flag inheritcheck nella direttiva .permissionset.
Raf? :-)
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 :-)
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 :-)
Da questo post del mio connazionale Radu Grigore, scopro un bellissimo teorema di McEliece (per la cronaca, Robert McEliece è stato negli anni '60 compagno di dottorato di ricerca con Donald Knuth, vero mostro sacro dell'informatica):
Any continuous strictly increasing function f : R -> R with the inverse g, g(Z) included in Z, has the dual properties:
ceiling(f(x)) = ceiling(f(ceiling(x)))
floor(f(x)) = floor(f(floor(x)))
OK, adesso so che volete un esempio semplice in C# :-)
System.Math.Sqrt è definita in {double non negativi} con valori in {double non negativi}, è
monotona strettamente crescente strettamente ascendente (si dice così?) e per argomenti interi non negativi...
Un pezzo di storia: nel video di questo post, dal minuto 6:13 al minuto 10:28, Brian Grunkemeyer racconta come gli è venuta l'idea delle classi statiche. E' iniziato tutto da un suo bug nella versione 1.0 del Framework: il metodo System.Environment.HasShutdownStarted non era ai tempi static, e quindi poteva essere chiamato solo tramite reflection :-)
Lo racconta anche in SLAR, alla pagina 178
Alla fine sono riuscito a individuare le opzioni giuste per compilare un programma vuoto (Foo.cpp) in Visual C++ .NET:
cl /CLR /LD Foo.cpp /link /NOENTRY
Lo schema dei colori corrisponde a quello del post precedente. Aggiungo sotto, altri 4 commenti ai 4 precedenti:
il codice IL più pulito per i programmi vuoti è generato dal compilatore Visual C# .NET (a parte, ovviamente, quello corrispondente all'IL stesso);
la dimensione della DLL per Visual C++ .NET è identica a quella della DLL per JScript .NET: 3584 bytes (coincidenza, secondo me, pazzesca! - anche perché gli IL sono diversi)
la direttiva .permissionset ha associata la SecurityAction RequestMinimum (System.Security.Permissions.SecurityAction.RequestMinimum)....
Non è detto che per leggere un file di configurazione (per esempio un web.config) di un'altra applicazione, bisogna per forza utilizzare le classi XML. Basta essere folli e scrivere una cosa come questa qui sotto, sparata oggi - e di cui ne sono contento :-)
In una web application Test, che ha questo web.config:
<?xml version="1.0" encoding="utf-8" ?><configuration> <appSettings> <add key="fooMessage" value="Hello World!" /> </appSettings></configuration>
aggiungiamo questa classe:
using System;using System.Configuration;using System.Collections.Specialized;namespace Ugidotnet.Configuration{ public sealed class CrossConfigSetter { private CrossConfigSetter(){} public const string DataName = "AppSettings"; public static void SetAppSettingsData() { NameValueCollection serializableAppSettings = new NameValueCollection(ConfigurationSettings.AppSettings); AppDomain.CurrentDomain.SetData(DataName, serializableAppSettings); } }}
mentre nella console application da...
Anche se in IL assembly una classe non eredita esplicitamente da [mscorlib]System.Object, se non specificata, questa ereditarietà è implicita anche a quel livello. Perciò:
.class A{}
viene trasformata dall'assembler in:
.class private auto ansi A extends [mscorlib]System.Object{}
Così, il fatto che System.Object sia la madre di tutti è assicurato al più basso livello possibile. Questo vuol dire che, anche se un compilatore volesse definire una classe al di fuori della gerarchia di classi che ha come root SystemObject, non potrebbe.
Il testo originale delle specifiche:
"If no type is specified, ilasm will add an extend clause to make the type inherit from System.Object."
Sapete perché il seguente codice C# non da errori a runtime mentre il corrispondente VB .NET sì (System.OverflowException)?
C#
VB .NET
int a = int.MaxValue;int b = a + 1;
Dim a As Integer = Integer.MaxValueDim b As Integer = a + 1
Semplicemente perché il compilatore C# traduce la somma nell'istruzione IL add, mentre il compilatore VB .NET la traduce nell'istruzione add.ovf (come add ma con la verifica di overflow). Più performante, ovviamente, la variante C#.
Spiegazione letta nel libro di Robinson (p. 21).
Spesso si confondono:
ILAsm = IL assembly (IL source code)
ilasm(.exe) = IL assembler
Questo è dovuto alla doppia rappresentazione di IL, sorgente e binaria, tra cui esiste una corrispondenza 1:1. A ogni istruzione di ILAsm corrisponde un opcode di IL che occupa 1 o al massimo 2 byte, direttamente parte dell'assembly. Quindi, quando parliamo di IL, sottintendiamo codice IL.
Questa doppia rappresentazione di IL è anche il motivo per cui ilasm non è un compilatore: solo sostituisce le mnemoniche di IL con i corrispondenti opcode.
Lo spiega bene Simon Robinson nel suo libro, "Expert .NET 1.1 Programming" (p. 2-3).
Il problema ormai è chiuso, dopo la raffica di post di Paolo (qui, qui, qui e qui), Cristian (qui) e Raffaele (qui, qui e qui).
Con un ritardo di una settimana, non faccio adesso altro che aggiungere due ciliegine (2 cents?) sulla torta:
Richter nel suo libro (pp. 443-444) avvertiva:
"Ogni qualvolta si ottiene un'analisi dello stack, è possibile rilevare che alcuni metodi dello stack di chiamate non sono visualizzati nell'analisi dello stack. Il motivo di questa assenza è rappresentato dal fatto che il compilatore JIT può eseguire l'inlining dei metodi per evitare l'overhead generato dalla chiamata e dalla restituzione di un metodo...
Richter nel suo libro (p. 444) dice:
"Il compilatore JIT (Just-In-Time) esamina l'attributo personalizzato System.Diagnostics.DebuggableAttribute applicato all'assembly. In genere, il compilatore applica questo attributo automaticamente. Se nell'attributo è specificato true per il parametro isJITOptimizerDisabled del costruttore DebuggableAttribute, il compilatore JIT non eseguirà l'inlining dei metodi dell'assembly. Per impostare questo parametro su true è possibile utilizzare l'opzione /debug del compilatore C#."
E' un po' strano secondo me il fatto che isJITOptimizerDisabled sia collegato all'opzione /debug (che sta nella categoria Debugging/Error Checking e ha il significato di Emit Debugging Information) e non all'opzione /optimize (della categoria Optimizations e che ha il significato Enable/Disable Optimizations)....
Abbiamo visto nel post precedente come il compilatore Delphi crea una classe finta per ogni unit così che anche il codice procedurale soddisfi la regola CLS 36. Cosa succede invece quando non gli lasciamo più spazio agli trucchi e trasgrediamo in modo esplicito una regola CLS, per esempio la CLS Rule 11 (quella sulla signature)? Potremmo da C# "consumare" un codice come quello di seguito? (al tipo Word di Delphi corrisponde in C# il tipo ushort, non CLS)
// DelphiCls.paslibrary DelphiCls;usesFoo in 'Foo.pas';// inutile: Delphi non è CLS-compliant[assembly: CLSCompliant(True)]beginend.
// Foo.pasunit Foo;interface// non ha una signature conforme al CLSfunction BadArgType(i: Word): Word;implementationfunction...
Esiste una regola CLS (CLS Rule 36) che dice: "Global static fields and methods are not CLS-compliant". Visto questo, vi aspettate che il seguente codice in Delphi for .NET sia CLS-compliant? Senza una classe, senza niente... :-)
// Delphi for .NETlibrary Foo;uses SysUtils;procedure DoSomething(msg: String);begin Console.WriteLine(msg)end;beginend.
Ebbene, sì! Il compilatore di Delphi for .NET, appunto per la conformità al CLS, trasforma la "procedura" DoSomething in un metodo static di una classe Foo sotto il namespace (se lo inventa lui) Foo.Units. E possiamo benissimo utilizzare la "procedura" DoSomething in C#:
// C#class Test{ static void Main() { Foo.Units.Foo.DoSomething("Ciao"); System.Console.Read(); }}
Ecco perché la mia sessione di giovedì si chiama "CLS:...
Ho notato delle piccole differenze tra alcuni messaggi d'errore generati dal compilatore C# e la documentazione di questi errori:
CS3006: "Overloaded method 'method' differing only in ref or out, or in array rank, is not CLS-compliant"
CS3012: "You must specify the CLSCompliant attribute on the assembly, not the module, to enable CLS compliance checking"
In arancione ho evidenziato i pezzi mancanti nella documentazione.
Nel file "errors.h" che contiene la lista di tutti gli errori e warning del compilatore C# del Rotor c'è scritto che "Range 3000-3999 is reserved for CLS errors". Qui sotto nella tabella, ho mappato gli errori C# riguardanti esplicitamente il CLS alle regole del CLS trasgredite:
Visual C# Compiler Error / Warning Number
Rotor Error Name
Summary
Description
CLS Rule Number
CLS Rule
?
ERR_CLS_NoVarArgs
?
?
?
?
CS3001
ERR_CLS_BadArgType
Argument type 'type' is not CLS-compliant
A public, protected, or protected internal method must accept a parameter whose type is compliant with the CLS.
11
All types appearing in a signature shall be CLS-compliant. (p. 49 del PDF)
CS3002
ERR_CLS_BadReturnType
Return type of 'method' is not CLS-compliant
A public, protected, or...
Mi ha attirato l'attenzione un commento di Massimo Prota a questo post di Pierre. Beh, riguarda CLS, di cui vi parlerò giovedì - a sala vuota mi sa... :-)
Massimo segnalava in sostanza il fatto che in un assembly conforme al CLS non possiamo nominare gli identificatori dei campi protected con il prefisso "_" in una classe public.
Ne approfitto per aggiungere delle cose magari meno conosciute:
Beccare tra le 41 regole del CLS quella responsabile di questo vincolo non è immediato. Dovreste leggervi l'appendice 7 "Programming Language Identifiers" dell'"Unicode Technical Report #15" (come specificato dalla CLS Rule 4 - p. 44 del...
Per ridurre la signatura delle variabili locali, IL permette di specificarle in degli slot indicizzati: [0], [1], etc. così da poter condividere uno slot con più variabili locali dello stesso tipo. Per esempio, a questo metodo Main:
static void Main(){ int i = 1;}
corrisponde questo codice IL in seguito a una compilazione con /debug+
.method private hidebysig static void Main() cil managed{ .entrypoint .maxstack 1 .locals init ([0] int32 i) ldc.i4.1 stloc.0 ret}
in cui si nota lo slot [0] per la variabile i. Però, se si compila con /debug-, lo slot sparisce:
.method private hidebysig static void Main() cil managed{ .entrypoint .maxstack 1 .locals init (int32 V_0) ldc.i4.1 stloc.0 ret}
Strano, mi aspettavo a un comportamento...
Un mese fa avevo scritto due post (questo e questo) su programmi vuoti in C#. Oggi vorrei completare il quadro anche per altri linguaggi .NET, con il tabellone qui sotto che spero parli un po' da sé. Non sono riuscito a individuare le opzioni giuste per il compilatore Visual C++ (cl.exe) (con i vostri commenti magari riuscirò a completare la tabella anche per C++ e, perché no, per altri linguaggi .NET).
Quattro commenti soltanto:
in Visual C# .NET non c'è bisogno di referenziare assembly che supportino
la compilazione e la generazione di codice (come "Microsoft.VisualBasic.dll",
"vjscor.dll", "Microsoft.JScript.dll");
Visual Basic .NET referenzia...
Supponiamo che vi troviate davanti a un quiz come questo:
class Foo{ static void Main() { string s1 = string.Empty; string s2 = ""; double d = System.Math.PI; }}
Secondo voi, quanti warning "The variable 'variable' is assigned but its value is never used" otterreste in compilazione? Qualcuno dirà 3, un altro 2, un altro ancora 1, mentre i più fighi diranno nessuno. La risposta però è 2. Per capire il motivo non mi sono stati d'aiuto né le specifiche, nè Richter, né Box, né Gunnerson. La risposta l'ho trovata spulciando nel Rotor, nel corpo dei metodi FUNCBREC::reportUnreferencedVars (le righe 668-683) e FUNCBREC::bindAssignment (le righe 7488-7533). Vediamo cosa...
Partendo dalla misteriosa frase:
"The type of a null-literal is the null type"
che si trova nelle specifiche, ho scritto finora quattro altri post: ("The null type?", "The null type (un po' più chiaro)", "The null type (la risposta di Dominic Cooney)", "Di nuovo sul nulla :-)"). Oggi però, finalmente ho trovato nei sorgenti di Rotor la definizione di questo tipo:
/** NULLSYM - represents the null type -- the type of the "null constant".*/class NULLSYM: public TYPESYM{};
e andando in su nella gerarchia dei simboli ho trovato:
SYM
the base symbol
PARENTSYM (deriva da SYM)
a symbol that can contain other symbols as children
TYPESYM (deriva da PARENTSYM)
a...
Per trasformare un DataSet in un Recordset hanno parlato nei loro libri Dino Esposito (vedi "Programmare XML in Microsoft .NET", pp. 170-179 e pp. 326-332) e Paolo Pialorsi con Marco Russo (vedi ".NET XML & WebServices Full Contact", pp. 27-35).
Magari pochi sanno la strada contraria: quella dal Recordset al DataSet. Per loro il seguente snippet:
public sealed class AdoConvert {
private AdoConvert(){}
public static DataSet ToDataSet(ADODB.Recordset rs) { OleDbDataAdapter da = new OleDbDataAdapter(); DataTable dt = new DataTable(); DataSet ds = new DataSet(); da.Fill(dt, rs); ds.Tables.Add(dt); return ds; } public static DataSet ToDataSet(string recordsetXml) { ADODB.Recordset rs = new ADODB.Recordset(); ADODB.Stream stream =...
Non poche cose si imparano provando a compilare programmi vuoti :-) Vediamone alcune:
Per esempio, vi dicevo nell'altro post che:
type foo.cs > foo.cscsc /t:library foo.cs
produce un assembly (DLL) di 3072 bytes identico a quello prodotto se foo.cs fosse fatto solo da namespace vuoti perché il concetto di namespace è al livello del linguaggio e non scende al livello IL. Quindi se i namespace non si "applicano" a nessun tipo il compilatore giustamente li ignora.
Il codice IL di questo assembly è:
.assembly extern mscorlib{.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ).ver 1:0:5000:0}.assembly foo{// --- The following custom attribute...
Vi sieti mai chiesti se i programmi vuoti (cioè da 0 bytes) compilano? Queste due righe nel VS .NET Command Prompt:
type foo.cs > foo.cscsc /t:library foo.cs
producono un DLL da 3072 bytes.
Forse per qualcuno sapere questo potrebbe sembrare una cosa di poco peso. Voglio però farvi notare che, se al posto del file vuoto compilate uno snippet che contiene un namespace vuoto:
namespace empty{}
risulterà un DLL della stessa dimensione di 3072 bytes. Se provate con ildasm a vedere il codice IL nei due casi, vedrete che sono identici.
Cosa vuol dire questo? Ci dobbiamo semplicemente ricordare che il concetto di namespace è al...
Michi, in questo post del 6 settembre intitolato "Reflection in disagreement with language spec" su MSDN Product Feedback Center, nota lo stesso comportamento segnalato più volte da me qui, qui e qui (e poi anche da Corrado, qui e qui)
Ecco la risposta di Chris (Microsoft), due giorni dopo:
"In general, the language spec does not need to be in agreement with the Type system. Many languages have different type systems which they then map to the CLR type system. The CLR type system is defined in the ECMA specs -- specifically ECMA Partition II specification -- not any specific language spec.
While...
Secondo il buon senso questo metodo dovrebbe ritornare sempre false. Invece ho notato con stupore che per alcuni type full name, ritorna true!
static bool IsGhostType(string typeFullName){ Type type = Type.GetType(typeFullName); if(type != null) { return !type.FullName.Equals(typeFullName); } return false;}
So che volete degli esempi :-) Eccone alcuni:
System.Object[*****] (è un System.Object[*])
System.Object[*,*,*,*,*] (è un System.Object[,,,,])
System.Object[*,,*,,*] (è un System.Object[,,,,])
La canzone del giorno: Nada - "Io l'ho fatto per amore" :-)
In questo post affermavo (in riferimento agli array unidimensionali non-vettori, cioè di limite inferiore diverso da zero) che:
"non è possibile accedere in modo indicizzato agli elementi dell'array (si devono utilizzare i metodi SetValue e GetValue)."
Lo si può fare invece tramite un ArrayList:
int lb = 100; // limite inferioreint ub = 1000; // limite superiore// int[] a = new int[lb..ub];Array a = Array.CreateInstance(typeof(int), new int[1]{ub - lb + 1}, new int[1]{lb});ArrayList al = ArrayList.Adapter(a);al[111] = 1;Console.WriteLine("al[{0}] = {1}", 111, al[111]);
Ho indagato un po' via reflection sul tipo ritornato da System.Type.GetType("System.Int32[*]").
Ha due costruttori, tutti e due instance: il primo costruttore ha un parametro int con significato di lunghezza dell'array (in questo caso il limite inferiore sarà zero!) mentre il secondo costruttore ha due parametri int: il primo parametro rappresenta il limite inferiore mentre il secondo parametro rappresenta la lunghezza meno il limite inferiore. Sono riuscito quindi a costruire un array unidimensionale non-vettore (cioè di limite inferiore diverso da zero) utilizzando System.Activator.CreateInstance (al posto di System.Array.CreateInstance).
Il codice relativo nei prossimi giorni, adesso ho un po' di fretta di andare a letto...
L'unico modo che ho trovato possibile per utilizzare gli array unidimensionali di limite inferiore diverso da zero è questo che risulta dal seguente snippet:
int lb = 100; // limite inferioreint ub = 1000; // limite superiore// int[] a = new int[lb..ub];Array a = Array.CreateInstance(typeof(int), new int[1]{ub - lb + 1}, new int[1]{lb});// a[111] = 1;a.SetValue(1, 111);Console.WriteLine("a[{0}] = {1}", 111, a.GetValue(111));
Si può verificare che:
il tipo di a è System.Int32[*] (ancora non ho capito bene il significato dell'asterisco che appare solo nel caso di un array unidimensionale creato con System.Array.CreateInstance e con il limite inferiore diverso da zero);
gli array unidimensionali col limite...
Posso creare array MULTIdimensionali con un limite inferiore diverso da zero; lo spiega Richter nel suo libro alla pagina 318 e poi con un copia e incolla dello stesso esempio in SLAR alla pagina 49.
Ma se voglio "estrapolare" lo stesso metodo per un array UNIdimensionale in una sintassi immaginaria:
int[] a = new int[lb..ub]; // questa sintassi non è ancora implementata!
dove lb è lower bound (per esempio 100) e ub è upper bound (per esempio 1000), scrivere:
int[] a;// dichiarare e inizializzare lb e ub...a = (int[])Array.CreateInstance(typeof(int), new int[1]{ub - lb + 1}, new int[1]{lb});
dà a run-time l'errore System.InvalidCastException ("Specified cast is...
In questo elenco sul mail server del Dipartimento di Informatica dell'Università di Pisa, troverete una serie di mailing list, alcune dedicate a .NET e di un livello che il nome di Pisa vi garantisce sempre.
Si sa che possiamo creare array con lower bound non zero, quindi è possibile avere un elemento a[..., i, ...] con i > a.Length. Mi sono chiesto allora quale sia il valore massimo che GetLowerBound e GetUpperBound possono restituire. Mi aspettavo che fosse int.MaxValue, invece per un lower bound int.MaxValue e un length 1 per una certa dimensione otteniamo System.ArgumentOutOfRangeException con il messaggio "Higher indices will exceed Int32.MaxValue because of large lower bound and/or length". Per fare il pignolo, nel nostro caso non abbiamo alcun "higher index" (la lunghezza è 1). Come mai l'errore?
Non si tratta di un test ma...
Se nel vostro assembly di test (NUnit) avete un file "App.config", dovete copiare questa riga esattamente così com'è, senza sostituire nulla:
copy "$(ProjectDir)app.config" "$(TargetDir)$(TargetFileName).config"
e incollarla in:
Common Properties > Build Events > General > Post-build Event Command Line
sotto le proprietà del vostro progetto di test.
Utilissimissimo tip se implementate per esempio il pattern Provider (come sto facendo io). Qualcuno conosce un'altra soluzione?
In C#, "integer literals have two possible forms: decimal and hexadecimal" (9.4.4.2) ma in Java "an integer literal may be expressed in decimal, hexadecimal, or octal" (3.10.1).
Per questo, 08 in C# è 8, mentre in Java si ottiene errore di compilazione "integer number too large" (il prefisso del literal di un intero in base 8 in Java è 0).
Post da ragazzini, lo so, però l'ho postato per distanziarmi da Corrado solo di 99 post... :-)
Un'eccellente introduzione a MSIL scritta da Kenny Kerr in una serie di post. Per stimolarvi l'appetito, qualche spunto:
"Unlike C#, CLI does not have any requirement that a method must belong to a class."
"It is also possible to omit the variable names. In that case you would refer to the variables by their zero-based index in the declaration."
"You must declare a constructor for a concrete reference type. Unlike languages like C# and C++, the IL assembler will not generate a constructor for you automatically."
"The CLI does not recognize namespaces as a distinct concept. Rather the full type name is always used."
"If...
Ieri Giancarlo mi ha chiesto via mail:
"Ho sempre avuto un dubbio... Perchè nella definizione di una interfaccia non c'è la possibilità di specificare anche una firma per eventuali costruttori?"
Gli rispondo qui per togliere eventualmente anche ad altri lo stesso dubbio.
Partiamo da Richter, p. 326 (come si deve):
"Il linguaggio CLR consente alle interfacce di contenere costruttori static.CLR non consente alle interfacce di contenere alcun costruttore di istanza."
e da questo banale snippet "Foo.cs":
interface IFoo{void NonUnCostruttore();}
Lo compiliamo:
csc /t:library Foo.cs
per ottenere il codice IL:
ildasm Foo.dll /out=Foo.il
Il metodo NonUnCostruttore avrà questa dichiarazione in IL:
.method public hidebysig newslot abstract virtual instance void NonUnCostruttore() cil managed
Con l'aiuto...
Ieri sera, dopo il post di Corrado, ho continuato a giocare un po' con le interfacce perché è venuto anche a me il dubbio di un possibile bug di IsSubclassOf... E lo snippet sotto lo dimostra:
using System;using System.Reflection;interface IFoo{}public class Foo{ static void Main() { BindingFlags all = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy; Type ifooType = typeof(IFoo); Type objectType = typeof(object); Console.WriteLine("{0} is{1} subclass of {2}", ifooType.Name, ifooType.IsSubclassOf(objectType) ? "" : "'nt", objectType.Name); Console.WriteLine("{0}\t#members = {1}", ifooType.Name, ifooType.GetMembers(all).Length); Console.WriteLine("{0}\t#members = {1}", objectType.Name, objectType.GetMembers(all).Length); Console.Read(); }}
L'output è questo:
IFoo is subclass of ObjectIFoo #members = 0Object #members = 14
Però, se IFoo fosse veramente "subclass" di Object, avremmo dovuto...
I numeri possibili nel Quiz Sharp #36 sono:
1 - Pow(10, -2043)
e
0 = 0 * Pow(10, Pow(10, 2043) - 1)
Per avere un'idea sulla grandezza del numero moltiplicato con 0, pensate che un googolplex è "solo" Pow(10, Pow(10, 100)).
Se al posto di 0 mettete 1, cioè 1e99...9, ovviamente il compilatore vi dà "Floating-point constant is outside the range of type 'double'". Come mai allora per 0e99...9 va tutto bene e il compilatore fornisce il risultato corretto? Il compilatore riconosce una moltiplicazione con 0 e chiude per il resto gli occhi. Quando tokenizza la parte exponent dell'espressione 0e99...9, non gli interessa di rappresentare...
Vi ricordate i miei punti di domanda sul null type? (qui,
qui
e qui)
Per forzare il compilatore a dire di più, ho scritto una follia del
genere:bool b = null.Equals(null, null)
e il compilatore ha risposto coll'errore CS0023:
"Operator '.' cannot be applied to operand of type '<null>'".
Non trovate niente di strano? "of type '<null>'" e non
"of type 'null'"!
Cosa dobbiamo capire? Che il nome del null type è "<null>"?
Tutti sanno del nesting separator (il segno "+") nel fully qualified name di una classe (in italiano come si dice?
nome completo?).
Ma non tutti sanno che il segno "+" può comparire anche all'interno
del nome di un namespace. Non in C#, però in IL sì.
Giochiamo un po':
// Foo.csnamespaceA.BC
{
class D{}
}
Compiliamo il codice C# con:
csc /target:library Foo.cs
otteniamo il codice IL:
ildasm Foo.dll /out=Foo.il
e in tutte e due le sezioni "CLASS STRUCTURE DECLARATION" e "CLASS MEMBERS DECLARATION"
la dichiarazione del namespace la cambiamo da:
.namespace A.BC
a:
.namespace 'A.B+C'
e poi compiliamo il codice IL con:
ilasm /DLL Foo.il
Sorpresi del messaggio "Operation completed successfully"?
Adesso il nome completo della classe...
Il seguente snippet compila:
using System;class Foo{ [Obsolete("", true)] static void Main() { Console.WriteLine("Hello Obsolete World!"); Console.Read(); }}
Ciò vuol dire che l'entrypoint Main non può mai essere Obsolete. Questo sicuramente ha senso, però mi aspettavo di vedere un warning oppure una nota a riguardo nelle specifiche.
A tutti string.Empty potrebbe sembrare un'espressione costante, ma in senso C# non lo è!
Empty è definito come campo static readonly e non come const, perciò non è una "constant expression" (C# Language Specification, 14.15).
Una conseguenza di questo fatto? Il seguente banalissimo snippet non compila!
using System;class FooAttribute: Attribute{ public FooAttribute(string arg){}}class Test{ [Foo(string.Empty)] static void DoSomething() { Console.WriteLine("I did something"); } static void Main() { DoSomething(); Console.Read(); }}
Otteniamo errore di compilazione: "An attribute argument must be a constant expression, typeof expression or array creation expression" e dobbiamo sostituire string.Empty con "" oppure con null.
Strano, vero?
Un paragrafo (p. 72) dal libro "Expert One-on-One Visual Basic .NET Business Objects" di Rockford Lhotka (MVP, Regional Director e Software Legend - cosa gli manca?) che viene a completare quello che è stato detto in questo post "Lettura dei dati e DbNull" del nostro M.rkino (MVP anche lui):
"Most of the time, we don't care about the difference between a null value and an empty value (such as an empty string or a zero), but databases often do. When we're retrieving data from a database, we need to handle the occurence of unexpected null values with code such as this:
If...
Non mi vergogno di dire che ho scoperto solo stasera il "Clipboard Ring".
Vi ricordate come Jay Roxe al "Whidbey Reloaded" del 8 luglio trascinava dal Toolbar pezzi di codice nella sua presentazione? A mio parere è comodissimo per gli speaker - si potrebbe sperimentare al prossimo workshop.
Una soluzione bellissima per fare single-stepping a livello IL, postata sulla ADVANCED-DOTNET mailing list di DevelopMentor da Serge Lidin (l'autore dell'"Inside Microsoft .NET IL Assembler")
Supponiamo di voler fare il debugging a livello IL di un programma Foo.cs. Dovremo procedere nel seguente modo (ho scelto una modalità da linea di comando per capire meglio i passi):
1. Visual C# .NET Compiler (Emit debugging information)csc Foo.cs /debug+
2. .NET Framework IL Disassembler (Show original source lines as comments, Compile to file with specified name)ildasm Foo.exe /source /out=Foo.il
3. .NET Framework IL Assembler (Include debug information)ilasm Foo.il /debug
4. Development Environment (Open the specified executable to be...
Abbiamo visto ieri, grazie all'osservazione di Nicu, che le specifiche avrebbero dovuto dire:
"Any of the static members declared by the class are referenced" al posto di:
"Any of the static members of the class are referenced".
Curioso, sono ricorso al libro di Richter e ho scoperto che lui fa la precisazione (p. 188) che manca nelle specifiche: quando parla di campo statico, aggiunge "non ereditato"! A Box invece, manca (p. 62) questa precisazione.
Ieri per tutto il giorno vi ho parlato dell'articolo di Nicu G. Fruja in cui porta alla luce "a few gaps and mistakes in the C# reference manual, inconsistencies with different implementations of C#". Gli ho scritto chiedendogli dei commenti su "The reference of the static field B.x triggers only the initialization of A" e oggi mi ha risposto così (traduco dal rumeno):
Le specifiche affermano erroneamente che:
(17.11) "The execution of a static constructor is triggered by the first of the following events to occur within an application domain:
An instance of the class is created.
Any of the static members...
"it is useful to think of static members as belonging to classes" (C# Language Specification, 17.2.5). Nel nostro caso (B.x), la classe è A. Si può vedere anche dal codice IL:
ldsfld int32 A::x
E' quindi naturale che si chiami il costruttore statico della classe A e non quello della classe B...
Avrei potuto proporlo come quiz, ma poi mi sono chiesto: "Perché essere sadici?" :-)
using System;class A{ public static int x; static A() { Console.WriteLine("Initializing A"); }}class B: A{ static B() { Console.WriteLine("Initializing B"); }}class Test{ static void Main() { Console.WriteLine("B.x = {0}", B.x); }}
Veramente non riesco a spiegarmi il fatto che questo snippet stampi:
Initializing AB.x = 0
Perché A??? Magari chiedo a Nicu e poi ve lo dico, se mi risponde :-)
Se qualcuno trova una spiegazione, varebbe la pena di postarla!
Se ci fosse stato class al post di struct, questo snippet:
using System;struct P{ static P() { Console.WriteLine("Initializing P"); }}class Test{ static void Main() { P p = new P(); }}
avrebbe stampato a console "Initializing P". Così invece, non stampa nulla! Dobbiamo quindi fare attenzione alla parola class in questo frammento di C# Language Specification (17.11):
"The execution of a static constructor is triggered by the first of the following events to occur within an application domain:
An instance of the class is created.
Any of the static members of the class are referenced."
"The creation of the struct instance p using the parameterless default instance constructor P() does not trigger the...
Questo snippet che può sembrare banale:
using System;class Test{ static void Main() { int[,][] a = new int[2,3][]; Console.WriteLine(a.GetType()); Console.Read(); }}
stampa a console System.Int32[][,] - magari qualcuno di noi si aspettava System.Int32[,][] :-)
Giocando con la decorazione con .entrypoint, ho fatto dei piccoli esperimenti.
In sostanza, in questi esperimenti, ho spostato la direttiva .entrypoint dal metodo Main al metodo MyMain, come vedrete negli esempi seguenti. Tutti i metodi MyMain li ho volutamente scelti con la signatura "sbagliata". Le signature corrette sono (secondo la "C# Language Specification", 10.1):
static void Main()
static void Main(string[] args)
static int Main()
static int Main(string[] args)
Sono partito da questa:
class Foo{ public static string MyMain() { return "Hello World"; } static void Main() { }}
Dopo il classico giro csc->ildasm->ilasm e poi esecuzione, ho incontrato in tutti gli esempi questo errore a runtime: System.MethodAccessException ma con differenti informazioni aggiuntive...
Sabato scorso, Stefano ha richiamato sul suo blog il mio post "Main non public?" in cui mi chiedevo "qual è la ragione per cui un metodo Main possa avere qualunque accessibilità...". E' passata una settimana e nessuno ha risposto. Provo allora io a dare una risposta.
Mi sono creato un file Foo.cs:
class Foo{ static void Main() { }}
Con:
csc Foo.cs
ho ottenuto l'eseguibile Foo.exe. L'ho passato poi a ildasm:
ildasm Foo.exe /out=Foo.il
per ottenere il codice IL:
.assembly extern mscorlib{ .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) .ver 1:0:5000:0}.assembly Foo{ .hash algorithm 0x00008004 .ver 0:0:0:0}.module Foo.exe.imagebase 0x00400000.subsystem 0x00000003.file alignment 512.corflags 0x00000001.class private auto ansi beforefieldinit Foo extends [mscorlib]System.Object{}.class private...
Appena premuto il bottone "Inserici" per il post precedente, scopro un altro quiz (sempre della Session TS-2575 di 2004 JavaOne Conference presentata da Joshua Bloch e Neal Gafter) irriproducibile in C#: "5. Shades of Gray" (per essere precisi, l'autore in realtà è Dominik Gruntz e il quiz appare per la prima volta un anno e mezzo fa su Journal of Object Technology in un articolo interessante, intitolato "C# and Java: The Smart Distinctions").
Si tratta di un'altra "feature" di Java: "Java makes a distinction between the namespace of fields, methods and nested classes within a class, i.e., it is possible to...
Mi sono scaricato i 151 MB di PDF della "2004 JavaOne Conference" e ne stavo facendo un giro veloce quando ho trovato... che cosa? ...una session fatta solo di quiz :-)
J. Bloch, N. Gafter, "Still More Programming Puzzlers", Session TS-2575, 2004 JavaOne Conference
Il bello è che uno di questi quiz "4. More of the Same", è irriproducibile in C#! :-) Come mai? Perchè il quiz in questione sfrutta una "feature" di Java secondo la quale "It is possible for a method to have the same name as a constructor" ma, consigliano gli autori, "Don't ever do it". C# è nato...
In un post del 10/06 "The null type" e in un altro del 14/06 "The null type (un po' più chiaro)", mi chiedevo cosa vuol dire "the null type" nella frase "The type of a null-literal is the null type". Ecco la risposta di Dominic Cooney (bravi anche i suoi fratelli! :-) Joseph e Patrick)
"Re: Adrian Florea's comments on the 'null' type,
As I understand it, the 'null' type doesn't exist outside verification. So, for example, if you're doing verification and a control flow path that does ldnull merges with one that does ldstr, the verification algorithm merges the null type...
Mi sto chiedendo qual è la ragione per cui un metodo Main possa avere qualunque accessibilità... Quale sarebbe secondo voi il significato di un metodo Main non public? In Java, a partire dalla versione 1.4 è obbligatorio dichiararlo public.
C# Language Specification, 2nd edition
Java Language Specification, 2nd edition
10.1"Specifically, the execution environment can access the application’s entry point regardless of its declared accessibility and regardless of the declared accessibility of its enclosing type declarations."
12.1.4"The method main must be declared public, static, and void. It must accept a single argument that is an array of strings"
Incuriosito da questo frammento dal libro di Box & Sells (l'edizione italiana, p. 119):
"L'attributo [System.Flags] influenza anche l'implementazione di ToString in modo che la rappresentazione in stringa del valore sarà una lista separata da virgole dei nomi dei membri anziché del singolo membro.",
ho giocato un po'. Per esempio, questo snippet:
using System;class Foo{ [Flags] enum NaN { Non = 1, un = 2, numero = 4, reale = 8 } static void Main() { NaN f = NaN.Non | NaN.un | NaN.numero | NaN.reale; Console.WriteLine(double.NaN.ToString(new System.Globalization.CultureInfo("it-IT"))); Console.WriteLine(f.ToString().Replace(",", string.Empty)); Console.Read(); }}
stampa a video:
Non un numero realeNon un numero reale
Un esempio meno folle :-) lo trovate nella documentazione di System.FlagsAttribute.Continuando a giocare, sono partito da questo snippet:
using System;class...
Questi giorni, oltre al progetto .NET su cui normalmente lavoro, dedico parte del tempo a preparare un corso di JSP e Servlet che terrò da settembre a dicembre. Sono ormai quasi 2 anni e mezzo da quando ho lasciato Java e sono scappato con .NET in un'inaspettata e dolcissima avventura :-) Guardo quindi Java con altri occhi, adesso che devo rivedere la materia.
Per esempio, il metodo getParameterValues dell'interfaccia javax.servlet.ServletRequest ritorna java.lang.String[] :
public java.lang.String[] getParameterValues(java.lang.String name)
"Returns an array of String objects containing all of the values the given request parameter has, or null if the parameter does not exist."
Già il fatto di ritornare...
Pensavo che tutti avessero già scoperto la soluzione, ma dopo aver visto che Paschal Leloup (non sono tanti i blogger .NET con quasi 1500 post...) ha dovuto cancellare completamente il .NET 2.0 appena installato, mi sono detto che forse vale la pena di postarla :-)
Quindi, è molto semplice: se dopo aver installato .NET 2.0 Beta 1, non vi va più niente nei "vecchi" progetti .NET 1.1 e ottenete:
"Error while trying to run project: Unable to start debugging on the web server.Click Help for more information."
allora andate subito in Internet Services Manager, click destro su Properties per il vostro local web site,...
Un po' di giorni fa segnalavo il fatto strano che questo codice:
class Foo{ static void Main() { System.Console.WriteLine(System.Void.Equals(null, null)); System.Console.Read(); }}
compilava (con Visual C# .NET Compiler version 7.10.3052.4) senza errori. Stasera ho scaricato e installato il ".NET Framework Version 2.0 Redistributable Package Beta 1 (x86)" uscito oggi e compilando (con Visual C# .NET Compiler version 8.00.40607.16) lo stesso codice di sopra ho ottenuto, finalmente, l'errore che aspettavo:
error CS0673: System.Void cannot be used from C# -- use typeof(void) to get the void type object.
Adesso sono più contento :-)
Adesso è più chiaro. Questo codice:
using System;interface IFoo{}class Foo{ static void Main() { Type i = typeof(IFoo); Type o = typeof(object); Console.WriteLine("{0} {1} da {2}", i.Name, i.IsSubclassOf(o) ? "deriva" : "non deriva", o.Name); Console.Read(); }}
scrive "IFoo deriva da Object" a console. Si può dire che le interfacce derivano in tutti gli effetti da System.Object.
Per chi non è ancora convinto, provi a vedere che il seguente codice compila:
using System;interface IFoo{}class Foo{ static void Main() { Console.WriteLine(IFoo.Equals(null, null)); Console.Read(); }}
e scrive True a console.
La proprietà Length di System.IO.Stream e di tutte le sue classi derivate non è l'unica proprietà read-only per cui esiste nella stessa classe un metodo setter ma lo sono anche le proprietà In, Out ed Error della classe System.Console:
public sealed class Console{ //... private static TextReader _in; public static TextReader In { get { //... } } public static void SetIn(TextReader newIn) { //... } private static TextReader _out; public static TextReader Out { get { //... } } public static void SetOut(TextWriter newOut) { //... } private static TextWriter _error; public static TextReader Error { get { //... } } public static void SetError(TextWriter newError) { //... } //...}
Ho verificato via reflection che questi sono gli unici casi all'interno del Framework di proprietà read-only pubbliche con setter pubblico nella stessa classe pubblica. Secondo me è interessante questa...
public abstract class Stream: MarshalByRefObject, IDisposable{ //... public abstract long Length {get;} public abstract void SetLength(long value); //...}
Proprietà read-only e setter come metodo...Secondo voi è stata una scelta oppure uno strano errore progettuale? Lo so, prima o poi scriverò una mail a Brad... :-)
Alla pagina 129 (p. 145 del file PDF) dello "Standard ECMA-334, C# Language Specification, 2nd edition", ho incontrato una frase curiosa:
"If T is an interface-type, the base types of T are the base interfaces of T and the class type object" (le sottolineature in tutte le citazioni sono mie)
Un tipo interfaccia ha come tipo base anche System.Object? In che modo dobbiamo interpretare questa affermazione? Solo il fatto che "le interfacce sono sempre considerate come tipi di riferimento"? (Richter, p. 330) Come possiamo verificare via reflection il fatto che un'interfaccia ha come tipo base la System.Object?
Sempre Richter dice, alla pagina...
Possono anche sembrarvi uguali i seguenti due snippet:
// Snippet 1using System;class Foo{ static void Main() { Console.WriteLine(Void.Equals(null, null)); Console.Read(); }}
// Snippet 2class Foo{ static void Main() { System.Console.WriteLine(System.Void.Equals(null, null)); System.Console.Read(); }}
ma non lo sono per niente! :-) Mentre il primo dà l'errore che tutti si aspettano:
System.Void cannot be used from C# -- use typeof(void) to get the void type object.
il secondo scrive senza problemi un bel True a console! :-) Dovrei pensare un po' al perché...
In riferimento alla mia perplessità sul null type, ho trovato alcuni spunti in questo documento: C++/CLI Language Specification. Candidate Base Document:
p. 43"The null type is a special type that exists solely to support the null literal, nullptr (also referred to as the null value constant). No instances of this type can be created; the only way to obtain a value of this type is via the nullptr literal, whose type is the null type."
p. 37"The null literal (nullptr) is not an lvalue."
p. 61"The sizeof operator shall not be applied to an expression that has null type."
L'equivalente di nullptr in C#...
In riferimento al mio post precedente, vediamo cosa dicono gli standard:
Java Language Specification, 2nd edition
Standard ECMA-334. C# Language Specification, 2nd edition
15.21.3While == may be used to compare references of type String, such an equality test determines whether or not the two operands refer to the same String object. The result is false if the operands are distinct String objects, even if they contain the same sequence of characters. The contents of two strings s and t can be tested for equality by the method invocation s.equals(t).3.10.5Strings computed at run time are newly created and therefore distinct.
14.9.7 (p. 167)Two...
Se vi chiedete perché 0 > -0 è False, la risposta è semplicissima: 0 == -0E se vi chiedete ancora perché 0 == -0, la risposta è nuovamente semplicissima :-)Il risultato dell'operatore:
int operator -(int x);
si ottiene sottraendo x da 0. Nel nostro caso: -0 = 0 - 0 = 0 quindi 0 == -0
L'articolo "(Not So) Stupid Questions: String Equality" su java.net, è nato dalle perplessità di Vladimir V. Ostromensky inviate alla redazione del sito, riguardanti il risultato "strano" del seguente codice Java:
Compile the following:
// StringTester.javapublic class StringTester{ public static void main(String args[]) { String aString = "myValue"; String bString = "myValue"; String cString = ""; if(args.length ==1 ) cString = args[0]; boolean test1 = aString.equals(bString); System.out.println("a.equals(b): " + aString + ".equals("+bString+") is " + test1); boolean test2 = aString == bString; System.out.println("a==b: " + aString + " == " + bString+" is " + test2); boolean test3 = aString.equals(cString); System.out.println("a.equals(c): " + aString + ".equals("+cString+") is " + test3); boolean test4 = aString...
Via Alex Papadimoulis, un modo più "simpatico" per inizializzare un array di n + 1 stringhe a string.Empty:
int n = 99;string[] arr = string.Empty.PadRight(n, ',').Split(',');
Ho modificato un po' il codice, il post originale si trova qui.
Una frase un po' misteriosa alla pagina 61 in basso nello standard ECMA-334 (C# Language Specification):
The type of a null-literal is the null type.
Voi cosa ci capite? Cosa vuol dire "the null type"?
Questo pezzo di codice, anche se è troppo scolastico, lo so..., magari permetterà ad alcuni di capire meglio perché MinValue = MaxValue + 1 e cosa vuol dire complemento binario:
using System;class Foo{ static void Main() { Console.WriteLine("MinValue in binario {0}", Convert.ToString(int.MinValue, 2).PadLeft(32, '0')); Console.WriteLine("MaxValue in binario {0}", Convert.ToString(int.MaxValue, 2).PadLeft(32, '0')); Console.WriteLine("MinValue {0} il complemento binario di MaxValue", int.MinValue == ~int.MaxValue ? "è" : "non è"); Console.Read(); }}
Responsabile per il loop infinito notato qui da M.rkino:
for(int i = int.MinValue; i <= int.MaxValue; i++){ Console.Write(".");}
è questa "identità": MaxValue + 1 = MinValue cioè, all'ultimo i++ il valore della i da MaxValue ridiventa MinValue.
I campi MaxValue e MinValue forniti dal tipo System.Int32 sono due costanti, definite rispettivamente 0x7fffffff (0 seguito da 31 1) e 0x80000000 (1 seguito da 31 0), cioè l'uno è il complemento binario dell'altro.
public struct Int32 //...{ public const int MaxValue = 0x7fffffff; public const int MinValue = unchecked((int)0x80000000); //...}
L'operatore unchecked si deve al fatto che la costante hexadecimale 0x80000000 è del tipo uint (Pow(2, 31)) e il cast ad un int produce overflow (Pow(2, 31) > MaxValue). Questi valori spiegano l'osservazione di M.rkino che Int32.MinValue = -Int32.MinValue.Vediamo qui di seguito come:Abbiamo:MinValue = -Pow(2, 31)MaxValue = Pow(2,...
In un programma "vuoto":
1 class Foo2 {3 static void Main()4 {5 }6 }
la sessione di debug inizia, nel caso di questo codice, direttamente con la riga 5 ("the next statement that will be executed").Poi, si sa che per il codice dopo un:for(;;);si ottiene un warning "Unreachable code detected", appunto perché il compilatore scopre il fatto che quella porzione di codice non sarà mai eseguita. Ciò vuol dire che, per il seguente codice:
1 class Foo2 {3 static void Main()4 {5 for(;;);6 }7 }
non esiste alcuna riga su cui il debugger si possa fermare! :-)
Sul valore di System.Double.Epsilon, la documentazione dice solo che "The value of this constant is 4.94065645841247e-324". Vediamo se, utilizzando la tabella della rappresentazione su 64 bit di un double, riusciamo a trovare il valore esatto di questa costante.Se 0<E<2047 allora -1023<E-1023<1024. Siamo quindi nel caso:Pow(-1,S)*Pow(2,-1022)*(0.F)Ma siccome Epsilon>0 abbiamo S=0 e:Epsilon = Pow(2,-1022)*(0.F)F è il numero minino, positivo e minore di 1, che si rappresenta in modo binario su 52 bit, quindi:F = Pow(2,-52)e Epsilon = Pow(2,-1022)*Pow(2,-52)cioè Epsilon = Pow(2,-1074).
Valore confermato da SLAR dove, alla pagina 157, si può leggere: "A System.Double can represent [...] the finite set of non-zero values...
Da uno scambio di messaggi questi giorni sul Messenger con M.rkino e Gianluca, sono usciti fuori alcuni aspetti della System.Double, a prima vista strani. Per esempio, questo di seguito, presentato da Brian Grunkemeyer in SLAR (p. 156):
"You might wonder why this simple code would throw an OverflowException:
string s = Double.MaxValue.ToString();double d = Convert.ToDouble(s);
The Convert.ToDouble() method throws an exception due to rounding errors. Note that the IEEE spec for doubles says that a double has about 15.7 decimal digits worth of precision, but to accurately represent that number you need to print out 17 digits to avoid rounding errors."
Lo standard a...
Console.WriteLine(Int16.MaxValue == (int)(4 * Double.Epsilon * Double.MaxValue * Int64.MaxValue));Console.WriteLine(Int16.MaxValue == 4 * (int)(Double.Epsilon * Double.MaxValue * Int64.MaxValue));
Anche il tooltip intellisense di Visual Studio "vede" erroneamente System.Enum come una struct (perché utlizza reflection):
"struct System.EnumProvides the base class for enumerations."
"typeof(System.Enum).IsXXX does behave strangely, and is more that likely a bug. We'll take a look at that soon, and hopefully have a fix if it's deemed a problem"leggo nel commento di Joel Pobar (PM SSCLI) su questo post recente di Brad Abrams.
Adesso vado a fare la nanna, ho perso tutta la sera con le stranezze di typeof(System.Enum).IsXXX
Nella documentazione della proprietà IsClass della classe System.Type, c'è scritto:"Gets a value indicating whether the Type is a class; that is, not a value type or interface"E' vero che gli insiemi {ValueType}, {Interface} e {Class} sono disgiunti però esiste un tipo t che non fa parte né dal {ValueType}, né dall'{Interface}, né dal {Class}, cioè l'espressione !t.IsClass && !t.IsValueType && !t.IsInterface è true. Questo tipo è System.Enum di cui ho parlato in un altro post, sempre oggi.
Vediamo come si potrebbe correggere la definizione. Innanzitutto, cos'è un tipo?"type declarations: class types, interface types, array types, value types, and enumeration types"In...
Una conseguenza interessante del fatto che "System.Enum is the only reference type that extends System.ValueType" (Brad Abrams, SLAR, p. 174) è quella che typeof(System.Enum).IsClass ritorna false! E' quindi l'unico tipo t all'interno del Framework per il quale l'espressione t.IsAbstract && !t.IsClass && !t.IsInterface è true!Tutto ciò si spiega guardando l'ultima parte del codice (quella sottolineata) della proprietà IsClass:public bool IsClass { get { return ((GetAttributeFlagsImpl() & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Class && !IsSubclassOf(Type.valueType)); }}dove valueType è:private static readonly Type valueType = typeof(System.ValueType);
La conclusione? La conclusione è che esiste una classe che non è classe :-) Meno male che è soltanto una. :-)
A riguardo del mio post "Non esistono delegate singlecast", la verifica che un tipo t sia delegate o no scopro che si poteva fare in modo più semplice richiamando IsSubclassOf:
t.IsSubclassOf(typeof(System.MulticastDelegate))
Sto guardando la classe System.Collections.Specialized.NameObjectCollectionBase e le sue classi derivate e non capisco la ragione del prefisso "Base" di alcuni suoi metodi protected (BaseAdd, BaseClear, BaseGet, BaseGetAllKeys, BaseGetAllValues, BaseGetKey, BaseHasKeys, BaseRemove, BaseRemoveAt, BaseSet). Questi metodi sono richiamati dai loro metodi corrispondenti (Add, Clear, Get, GetAllKeys, GetAllValues, GetKey, HasKeys, Remove, RemoveAt, Set) nelle classi derivate ma, secondo me, queste richiamate andavano fatte come base.Method() anziché BaseMethod() e i metodi di forma BaseMethod nella classe base rinominati Method. In più, NameObjectCollectionBase sembra l'unica classe che abbia questo pattern strano.Un po' di refactoring o qualcosa mi sfugge?
Ho trovato all'interno del Framework tre interfacce di questa forma:public interface IServiceNameProvider{ ServiceResultType GetServiceName(ServiceParameterType param);}una specie di Strategy.
Service Name
Interface
Method
Service Result
Service Parameter
Format
System.IFormatProvider
GetFormat
System.Object
System.Type
Service
System.IServiceProvider
GetService
System.Object
System.Type
HashCode
System.Collections.IHashCodeProvider
GetHashCode
System.Int32
System.Object
Esiste una classe (System.NotSupportedException ma ce ne sono anche altre) che ha un ruolo più profondo nella progettazione in generale di una gerarchia di classi, quasi come se fosse una feature di linguaggio:"There are methods that are not supported in the base class, with the expectation that these methods will be implemented in the derived classes instead. The derived class might implement only a subset of the methods from the base class, and throw NotSupportedException for the unsupported methods." (.NET Framework Class Library Reference)"A typical scenario is when a base class declares a method that derived classes are required to...
Per chi non vuole chiamare MS PSS per risolvere KB 818803 in .NET 1.1. le patch si possono scaricare da InstantASP per Windows 2000 e per Windows 2003 Server
Per chi lavora con Infragistics NetAdvantage 2004 Volume 1 e vuole utilizzare Infragistics.WebUI.UltraWebNavigator.UltraWebMenu non solo in maniera drag-and-drop del componente, un workaround per evitare System.NullReferenceException è questo:
sostituire la riga: string appDir = Page.Request.ServerVariables["APPL_PHYSICAL_PATH"];nel metodo: public int ReadXmlFile(string, bool, bool)della classe: Infragistics.WebUI.UltraWebNavigator.UltraWebMenu ("UltraMenu.cs")con: string appDir = System.Web.HttpContext.Current.Request.ServerVariables["APPL_PHYSICAL_PATH"];
Joel Marcey, che firma la prefazione del libro di Brad Abrams, è anche l'autore di questo piccolo e simpatico tool di cui racconta nella prefazione, scritto in C# (un'unica classe, Intel.DSL.ECMA.DocGen) per trasformare le specifiche CLI da XML a Word. Adesso si chiama, un po' pomposo :-), ECMA TR/84 e lo trovate qui (20,2 MB), sorgenti compresi (31,1 KB) :-)
Per essere ancora più dottori in dotcctor (.cctor è l'abbreviazione di costruttore di classe), a parte la lettura del post di Brad Abrams segnalato oggi da Corrado e degli articoli di Jon Skeet e Satya Komatineni, otterrete un'immagine completa su questo argomento leggendo 4 pagine (pp. 187-190) nel libro di Richter e 3 (pp. 60-62) nel libro di Box e Sells. E se dopo ne avete ancora voglia e desiderate andare veramente fino in fondo, leggete per ultimo questo post del micidiale Chris Brumme.
Molto interessante una delle proposte di Jon Skeet alla fine del suo articolo:"An attribute would be a perfectly reasonable solution...
In VJ# è possibile tramite VS passare /win32res al compilatore. In VC#, proprio no...
Per chi, come me, deve utilizzare file di risorse unmanaged (Win32) in applicazioni .NET, questo articolo di Kenny Kerr, "Icon Browser: An Exercise in Resource Management" sarà di grande aiuto.Nel mio progetto, un porting di un prodotto client/server scritto in VB6 in un'applicazione web scritta in ASP.NET e C#, devo utilizzare le icone di un file .RES (non è proprio banale)
Per vedere se un tipo t è delegate o no, basta verificare se deriva da System.MulticastDelegate:
t.BaseType != null && t.BaseType.Equals(typeof(System.MulticastDelegate))
cioè non è necessaria la verifica singlecast (System.Delegate). La storia strana che spiega tutto ciò la potete leggere nel libro di Richter (pp. 375-376 per la traduzione italiana) e nel post di Brad Abrams.
Pierre segnala qua un benchmark di C# per calcoli scientifici. Io ne ho trovato un altro, sempre recente. Mi è rimasto un debole per queste cose visti i quattro anni ('92-'96) spesi pensando a robe CFD e a tanta matematica... Per chi ha la curiosità di leggere le conclusioni dell'inchiesta sul citato fallimento di ARIANE5
Io non sarei così radicale come Ian Griffiths nel suo commento sul post di Dino: "If you ever use new at all, you should aim to remove it at the earliest opportunity. It's not something you would ever choose to design in". Basta fare una ricerca nei sorgenti di Rotor per incontrare più volte public new per esempio e non penso che gli sviluppatori del framework non abbiano trovato la "earliest opportunity" per togliere via new :-)
Il campo System.IO.StreamWriter.Null per esempio "nasconde" il campo System.IO.TextWriter.Null da cui deriva e a prima vista non vedo come si poteva scrivere senza new...
Chi altro poteva rispondere meglio alla domanda di Dino se non appunto il papà di C# Anders Hejlsberg nella sua eccellente conversazione con Bill Venners su versioning (cioè new), virtual and override?
E gli argomenti mi sembrano molto "real-world", come chiedeva Dino
Scopro direttamente dalla bocca di Peter de Jong, il papà della serializzazione .NET (cioè dalle sue slide presentate al secondo Rotor Workshop come "History, Architecture, and Implementation of the CLR Serialization and Formatter classes") che:
Constructor is not in Interface, so compiler can’t check whether it presentConstructor isn’t inherited, so each subclass needs its own constructorEarlier version used SetObjectData instead of constructor
e più avanti nella conferenza:
ISerializable underwent many changes
E' quello che notavo in un mio post anteriore sull'asimmetria dell'ISerializable - quindi nelle prime versioni c'era anche SetObjectData! Sicuramente motivi di performance hanno determinato il disegno attuale.
Per chi vuole vedere il...
L'interfaccia ISerializable presenta un'asimmetria su cui secondo me varrebbe la pena di riflettere in modo più profondo:
il suo metodo GetObjectData offre al formattatore un container (SerializationInfo) con tutti i dati necessari per la serializzazione e un altro (StreamingContext) con tutti i, chiamiamoli, metadati. Al contrario, per la deserializzazione, non si trova un simmetrico SetObjectData (come qualcuno si potrebbe aspettare insieme a Christoph Schittko) ma un costruttore protected per la classe che implementa l'interfaccia, costruttore che deve avere gli stessi parametri con il metodo GetObjectData - l'esistenza di questo però non la costringe nessuno... Un processo così simmetrico come quello della serializzazione/deserializzazione,...