2013-01-16 19 views
8
  • C'è un modo per marcare un tipo (o, meglio ancora, un'interfaccia) in modo che nessuna istanza di esso possono essere memorizzati in un campo (in modo simile a TypedReference e ArgIterator)?
  • Allo stesso modo, c'è un modo per impedire che le istanze vengano passate attraverso metodi anonimi e - In generale - Per simulare il comportamento dei due tipi sopra?
  • Questo può essere fatto tramite ILDasm o, più in generale, attraverso l'editing di IL? Dal momento che UnconstrainedMelody ottiene risultati normalmente non ottenibili attraverso la modifica binaria di un assembly compilato, forse esiste un modo per "contrassegnare" determinati tipi (o anche meglio, quelli astratti o interfacce marker) attraverso lo stesso approccio.

dubito che hardcoded nel compilatore perché i documentation for the error CS0610 stati:di digitare make non memorizzabili

Ci sono alcuni tipi che non possono essere utilizzati come campi o proprietà. Questi tipi includono ...

Che a mio avviso suggerisce che l'insieme di tipi come quelli può essere esteso - Ma potrei sbagliarmi.

Ho cercato un po 'su SO e anche se ho capito che non è possibile eseguire throwing a compiler error a livello di codice, non ho trovato alcuna fonte che affermasse che determinati comportamenti di tipi "speciali" non potevano essere replicati.

Anche se la domanda è per lo più accademica, potrebbero esserci alcuni usi per una risposta. Ad esempio, a volte può essere utile assicurarsi che la vita di un certo oggetto sia vincolata al blocco del metodo che la crea.

MODIFICA:RuntimeArgumentHandle è un altro (non menzionato) tipo non memorizzabile.

EDIT 2: Se può essere di qualche utilità, sembra che il CLR tratta tali tipi in modo diverso e, se non solo il compilatore (ancora assumendo che i tipi sono in alcun modo diverso dagli altri). Il seguente programma, ad esempio, genererà uno TypeLoadException relativo a TypedReference*. L'ho adattato per renderlo più breve ma puoi aggirare tutto ciò che desideri. La modifica del tipo di puntatore, ad esempio, void* non genera l'eccezione.

using System; 

unsafe static class Program 
{ 
    static TypedReference* _tr; 

    static void Main(string[] args) 
    { 
     _tr = (TypedReference*) IntPtr.Zero; 
    } 
} 
+5

Non sarei affatto sorpreso se * fosse * hardcoded, btw. Solo perché la documentazione non vuole ridurla non vuol dire che non sia così nel compilatore. –

+0

Penso la stessa cosa, ma dovrò controllare. –

+1

[CS0611] (http://msdn.microsoft.com/en-US/library/bfca7x6z (v = vs80) .aspx) è un errore correlato, anche se probabilmente la soluzione per entrambi si applicherebbe a entrambi. – Mir

risposta

5

OK. Questa non è un'analisi abbastanza completa, ma sospetto che sia sufficiente per determinare se è possibile farlo, anche girando IL - che, per quanto posso dire, non è possibile.

anche io, a guardare la versione decompilato con dotPeek, non riuscivo a vedere nulla di speciale in quel particolare tipo/i tipi particolari in là, attributo-saggio o altro:

namespace System 
{ 
    /// <summary> 
    /// Describes objects that contain both a managed pointer to a location and a runtime representation of the type that may be stored at that location. 
    /// </summary> 
    /// <filterpriority>2</filterpriority> 
    [ComVisible(true)] 
    [CLSCompliant(false)] 
    public struct TypedReference 
    { 

Quindi, quello fatto , ho provato la creazione di una tale classe utilizzando System.Reflection.Emit:

namespace NonStorableTest 
{ 
    //public class Invalid 
    //{ 
    // public TypedReference i; 
    //} 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      AssemblyBuilder asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("EmitNonStorable"), 
                         AssemblyBuilderAccess.RunAndSave); 

      ModuleBuilder moduleBuilder = asmBuilder.DefineDynamicModule("EmitNonStorable", "EmitNonStorable.dll"); 

      TypeBuilder invalidBuilder = moduleBuilder.DefineType("EmitNonStorable.Invalid", 
                    TypeAttributes.Class | TypeAttributes.Public); 

      ConstructorBuilder constructorBuilder = invalidBuilder.DefineDefaultConstructor(MethodAttributes.Public); 

      FieldBuilder fieldI = invalidBuilder.DefineField("i", typeof (TypedReference), FieldAttributes.Public); 

      invalidBuilder.CreateType(); 
      asmBuilder.Save("EmitNonStorable.dll"); 

      Console.ReadLine(); 
     } 
    } 
} 

che, quando lo si esegue, getta una TypeLoadException, l'analisi dello stack di cui si punta a System.Reflection.Emit.TypeBuilder.TermCreateClass. Allora sono andato, dopo che con il decompilatore, che mi ha dato questo:

[SuppressUnmanagedCodeSecurity] 
[SecurityCritical] 
[DllImport("QCall", CharSet = CharSet.Unicode)] 
private static void TermCreateClass(RuntimeModule module, int tk, ObjectHandleOnStack type); 

Indicando nelle parti non gestite del CLR. A questo punto, per non essere sconfitto, ho scavato nelle fonti condivise per la versione di riferimento del CLR. Non passerò attraverso tutto quello che ho fatto, per evitare di gonfiore questa risposta oltre ogni ragionevole uso, ma alla fine, finisci in \ clr \ src \ vm \ class.cpp, nella funzione MethodTableBuilder :: SetupMethodTable2 (che appare anche impostare descrittori di campo), dove si trovano queste righe:

// Mark the special types that have embeded stack poitners in them 
         if (strcmp(name, "ArgIterator") == 0 || strcmp(name, "RuntimeArgumentHandle") == 0) 
          pClass->SetContainsStackPtr(); 

e

if (pMT->GetInternalCorElementType() == ELEMENT_TYPE_TYPEDBYREF) 
          pClass->SetContainsStackPtr(); 

quest'ultimo relativa alle informazioni che si trovano in \ src \ inc \ cortypeinfo.h, in tal modo:

// This describes information about the COM+ primitive types 

// TYPEINFO(enumName,    className,   size,   gcType,   isArray,isPrim, isFloat,isModifier) 

[...] 

TYPEINFO(ELEMENT_TYPE_TYPEDBYREF, "System", "TypedReference",2*sizeof(void*), TYPE_GC_BYREF, false, false, false, false) 

(In realtà è l'unico tipo ELEMENT_TYPE_TYPEDBYREF in quell'elenco.)

ContainsStackPtr() viene quindi utilizzato a sua volta altrove in varie posizioni per impedire l'utilizzo di quei tipi particolari, inclusi i campi - da \ src \ vm \ class. cp, MethodTableBuilder :: InitializeFieldDescs():

// If it is an illegal type, say so 
if (pByValueClass->ContainsStackPtr()) 
{ 
    BuildMethodTableThrowException(COR_E_BADIMAGEFORMAT, IDS_CLASSLOAD_BAD_FIELD, mdTokenNil); 
} 

in ogni caso: a tagliare una lunga, lunga, lunga storia breve, sembrerebbe essere il caso che i tipi non sono memorizzabili in questo modo è efficace hard- codificato nel CLR, e quindi che se vuoi cambiare la lista o fornire un IL significa contrassegnare i tipi come non memorizzabili, devi praticamente prendere Mono o il CLR della sorgente condivisa e far girare il tuo proprio contro n.

+1

Ottima risposta, penso che lo risolva. – Mir

2

Non ho trovato nulla nell'IL che indica che quei tipi sono in qualche modo speciali. So che il compilatore ha molti, molti casi speciali, come trasformare int/Int32 nel tipo interno int32 o molte cose relative alla struttura Nullable. Ho il sospetto che questi tipi siano anche casi speciali.

Una possibile soluzione sarebbe Roslyn, che mi aspetto consentirebbe di creare un tale vincolo.

+0

Non ho ancora cercato molto su Roslyn ma presumo che l'utente finale, utilizzando un normale compilatore, non sia interessato dal vincolo. Aspetterò di vedere se qualcuno può inventare un contro-argomento o una conferma (esaminando il compilatore, in qualche modo). – Mir

+0

@Eve Immagino che se stai facendo qualcosa di così raro, saresti in grado di usare Roslyn senza problemi. Naturalmente, se è di tipo pubblico, potresti non essere in grado di farlo. –

+0

Come pensi che Roslyn possa essere d'aiuto? – svick