2012-01-28 8 views
6

Tengo un conjunto de clases, cada una de las cuales puede abrir diferentes tipos de archivos usando una aplicación externa y decirle a esa aplicación que imprima el archivo en una impresora en particular. Todas las clases heredan una clase abstracta común y una interfaz.Diccionario de tipos de clase

internal interface IApplicationPrinter : IDisposable 
{ 
    string ApplicationExe { get; } 
    string ApplicationName { get; } 
    string[] PrintableExtensions { get; } 

    IApplicationPrinter CreateInstance(string Filename, string Printer); 
    void Print(); 
    bool ExitApplicationAfterPrint { get; set; } 
    bool WaitApplicationExitOnPrint { get; set; } 
    System.IO.FileInfo PdfFile { get; protected set; } 
} 
internal abstract class ApplicationPrinter : IApplicationPrinter 
{ 
    ... 
} 
internal class WordPrinter : ApplicationPrinter 
{ 
    internal static string[] PrintableExtensions { get { return new string[]{".doc", ".docx" }; } } 
    ... 
} 
internal class ExcelPrinter : ApplicationPrinter 
{ 
    internal static string[] PrintableExtensions { get { return new string[]{".xls", ".xlsx" }; } } 
    ... 
} 

estoy tratando de crear un Dictionary de extensiones de archivo para imprimir y que corresponde Type s de clases que pueden imprimir dichos archivos. Hago no quiero crear una instancia de las clases en el diccionario.

private static Dictionary<string, Type> FileConverters; 
static Printer() 
{ 
    FileConverters = new Dictionary<string, Type>(); 

    foreach (string ext in WordPrinter.PrintableExtensions) 
    { 
     FileConverters.Add(ext, typeof(WordPrinter)); 
    } 

    string filename = "textfile.txt"; 
    string extension = filename.Substring(filename.LastIndexOf(".")); 

    if (FileConverters.ContainsKey(extension)) 
    { 
     IApplicationPrinter printer = ((IApplicationPrinter)FileConverters[extension]).CreateInstance(filename, "Printer"); 
     printer.Print(); 
    } 
} 

¿Hay alguna manera de hacer Dictionary<string, Type> FileConverters con seguridad de tipos más, restringiendo a los valores que implementan IApplicationPrinter? En otras palabras, es algo como esto sea posible:

private static Dictionary<string, T> FileConverters where T: IApplicationPrinter; 

Actualización: no quiero para almacenar instancias por las siguientes dos razones:

  1. Cada clase puede manejar varios tipos de archivos diferentes (ver string[] PrintableExtensions) El diccionario almacena extensiones como claves. No hay utilidad en crear y almacenar múltiples separa la instancia de la misma clase.
  2. Cada clase de impresora utiliza COM API y Office Interop para crear instancias de aplicaciones de terceros. Es mejor que se cree una nueva instancia de cada clase para un trabajo de impresión cuando sea necesario y que el recolector de basura pueda realizar una limpieza posterior.
+0

¿Por qué no quiere almacenar instancias en el diccionario? – svick

Respuesta

2

No directamente - recuerde que las cosas que está poniendo en su diccionario son objetos Type, no objetos que implementan IApplicationPrinter.

Probablemente la mejor opción aquí es comprobar que cada tipo que agregue a su diccionario implemente IApplicationPrinter, marcando si type.GetInterface("IApplicationPrinter") devuelve nulo o no.

+0

Creo que es mejor evitar cadenas en lugar de tipos, siempre que sea posible. Y es posible aquí. – svick

+0

Tiene razón - typeof (IApplicationPrinter) .Name sería mejor que "IApplicationPrinter". –

+0

Quise decir algo más en la línea de 'typeof (IApplicationPrinter) .IsAssignableFrom (type)'. Totalmente seguro y sin cadenas de ningún tipo. – svick

1

Si usaste Dictionary<string, IApplicationPrinter>, eso no necesariamente significa que tendrás que tener una instancia diferente para cada string, varios de ellos pueden compartir la misma instancia.

Si no quiere hacer eso, puede almacenar fábricas en el diccionario. La fábrica puede ser un objeto que implementa una interfaz (algo así como IApplicationPrinterFactory), o simplemente un delegado que puede crear el objeto. En su caso, eso sería Dictionary<string, Func<IApplicationPrinter>>. Hacerlo de esta manera es completamente seguro. Para añadir al diccionario, que haría algo como:

FileConverters = new Dictionary<string, Func<IApplicationPrinter>>(); 

Func<IApplicationPrinter> printerFactory =() => new WordPrinter(); 

foreach (string ext in WordPrinter.PrintableExtensions) 
    FileConverters.Add(ext, printerFactory); 

Si está seguro de que quiere Dictionary<string, Type>, no hay manera de limitar que, por lo que todos los tipos de no implementar IApplicationPrinter. Lo que podría hacer es crear su propio diccionario, que verifica el tipo al agregarlo. Esto no hace que el tiempo de compilación sea seguro, pero lo hace más seguro en el tiempo de ejecución.

class TypeDictionary : IDictionary<string, Type> 
{ 
    private readonly Type m_typeToLimit; 
    readonly IDictionary<string, Type> m_dictionary = 
     new Dictionary<string, Type>(); 

    public TypeDictionary(Type typeToLimit) 
    { 
     m_typeToLimit = typeToLimit; 
    } 

    public void Add(string key, Type value) 
    { 
     if (!m_typeToLimit.IsAssignableFrom(value)) 
      throw new InvalidOperationException(); 

     m_dictionary.Add(key, value); 
    } 

    public int Count 
    { 
     get { return m_dictionary.Count; } 
    } 

    public void Clear() 
    { 
     m_dictionary.Clear(); 
    } 

    // the rest of members of IDictionary 
} 
3

lo haría de forma ligeramente diferente:

private Dictionary<String, Func<IApplicationPrinter>> _converters; 

public void Initialise() 
{ 
    foreach (string ext in WordPrinter.PrintableExtensions) 
    { 
     _converters.Add(ext,() => new WordPrinter()); 
    } 
} 

public IApplicationPrinter GetPrinterFor(String extension) 
{ 
    if (_converters.ContainsKey(extension)) //case sensitive! 
    { 
     return _converters[extension].Invoke(); 
    } 

    throw new PrinterNotFoundException(extension); 
} 

Este método no almacenará los casos en el diccionario como sea necesario, y creará una nueva instancia que cada vez que llame GetPrinterFor.También se tipea con más fuerza, ya que el tipo de devolución del Func<> tiene que ser un IApplicationPrinter.

+0

¡He estado buscando esta solución! Gracias Pondidum, ya no es necesario para un gran 'if() nuevo A(); else if() new B(); else if() new C(); else if() new ... 'en mi código. –

0

Primero debe cambiar su enfoque para acceder al conjunto de extensiones porque no puede acceder a su propiedad sin crear una instancia. Utilizaría un attribute personalizado para indicarle a tu diccionario de tipo qué extensión admite cada clase. Esto se verá así:

[PrinterExtensions("txt", "doc")] 
public class WordPrinter 
{ 
    .... // Code goes here 
} 

El uso del atributo le permite restringir el tipo. Hay dos formas de archivar esto.

  • Lanza una excepción en el constructor del tipo (ver this link)
  • utilizar una clase abstracta en lugar de una interfaz, esto le permite restringir que clase utilizando un atributo protegido (en este punto es necesario crear una clase base para el atributo para acceder desde tu diccionario de tipos) que solo se puede aplicar a las clases desvinculadas.

Ahora usted puede simplemente puede simplemente comprobar si una clase particular tiene el atributo PrinterExtensions y acceder a una propiedad de la misma que recupera todas las extensiones o llamar a un método que registra directamente todas las extensiones.

Cuestiones relacionadas