2012-01-28 2 views
6

У меня есть набор классов, каждый из которых может открывать файлы разных типов с помощью внешнего приложения и сообщать этому приложению о печати файла на конкретный принтер. Все классы наследуют общий абстрактный класс и интерфейс.Словарь классов классов

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" }; } } 
    ... 
} 

Я пытаюсь создать Dictionary печатаемых расширения файлов и соответствующий Type х классов, которые могут печатать такие файлы. Я делаю не хочу создать экземпляр классов в словаре.

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(); 
    } 
} 

Есть ли способ, чтобы сделать более типобезопасным Dictionary<string, Type> FileConverters, ограничивая его до значений, которые реализуют IApplicationPrinter? Другими словами, это что-то вроде этого можно:

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

Update: Я не хочу, чтобы хранить экземпляры для следующих двух причин:

  1. Каждый класс может обрабатывать несколько файлов различных типов (см string[] PrintableExtensions). Словарь хранит расширения как ключи. В создании и хранении нескольких экземпляров разделов одного и того же класса нет никакой полезности.
  2. Каждый класс принтера использует COM API и Office Interop для создания экземпляров сторонних приложений. Лучше, чтобы новый экземпляр каждого класса был создан для задания печати, когда это требуется, и что сборщик мусора может впоследствии очистить.
+0

Почему вы не хотите хранить экземпляры в словаре? – svick

ответ

2

Непосредственно - помните, что вещи, которые вы вкладываете в словарь, - это Type объекты, а не объекты, которые реализуют IApplicationPrinter.

Вероятно, лучший вариант здесь - проверить, что каждый тип, который вы добавляете в словарь, реализует IApplicationPrinter, проверяя, возвращает ли номер type.GetInterface("IApplicationPrinter") null или нет.

+0

Я думаю, что лучше избегать строк вместо типов, где это возможно. И это возможно здесь. – svick

+0

Вы правы - typeof (IApplicationPrinter). Имя будет лучше, чем «IApplicationPrinter». –

+0

Я имел в виду нечто большее по строкам 'typeof (IApplicationPrinter) .IsAssignableFrom (type)'. Полностью безопасно и без каких-либо струн. – svick

1

Если вы использовали Dictionary<string, IApplicationPrinter>, это не обязательно означает, что вам нужно будет иметь отдельный экземпляр для каждого string, некоторые из них могут использовать один и тот же экземпляр.

Если вы не хотите этого делать, вы можете хранить фабрики в словаре. Фабрика может быть объектом, который реализует интерфейс (что-то вроде IApplicationPrinterFactory) или просто делегат, который может создать объект. В вашем случае это будет Dictionary<string, Func<IApplicationPrinter>>. Выполнение этого способа полностью безопасно. Чтобы добавить в словарь, вы могли бы сделать что-то вроде:

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

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

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

Если вы уверены, что хотите Dictionary<string, Type>, нет никакого способа, чтобы ограничить это, так что все типы в там реализовать IApplicationPrinter. Что вы можете сделать, так это создать свой собственный словарь, который проверяет тип при добавлении. Это не гарантирует безопасность компиляции, но делает ее более безопасной.

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

Я хотел бы сделать это немного по-другому:

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); 
} 

Этот метод не будет хранить экземпляры в словаре, как вам требуется, и создаст вам новый экземпляр каждый раз, когда вы называете GetPrinterFor.Он также более строго типизирован, так как возвращаемый тип Func<> должен быть IApplicationPrinter.

+0

Я искал это решение! Спасибо Pondidum, больше не нужно для огромного 'if() нового A(); else if() new B(); else if() new C(); else if() new ... 'в моем коде. –

0

Прежде всего вам нужно изменить свой подход для доступа к массиву расширений, потому что вы не можете получить доступ к своему свойству без создания экземпляра. Я бы использовал пользовательский attribute, чтобы сообщить вашему типу-словарю, какое расширение поддерживает каждый класс. Это будет выглядеть так:

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

Использование атрибута позволяет вам ограничить тип. Существует два способа архивирования.

  • Бросить исключение в конструкторе типа (см this link)
  • Использовать абстрактный класс вместо интерфейса, это позволяет ограничить вас класс с помощью защищенного атрибута (в этот момент вам необходимо создать базовый класс для атрибута для доступа к нему из вашего словаря типов), который может быть применен только к разрозненным классам.

Теперь вы можете просто можно просто проверить, если конкретный класс имеет атрибут PrinterExtensions и получить доступ к свойству его, который извлекает все расширения или вызов метода, который непосредственно регистрирует все расширения.

Смежные вопросы