Class Type Dictionary

I have a set of classes, each of which can open different types of files using an external application and tell this application to print the file to a specific printer. All classes inherit a common abstract class and interface.

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

I am trying to create a Dictionary for print extensions and corresponding Type classes that can print such files. I do not want to instantiate the classes in the dictionary.

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

Is there a way to make Dictionary<string, Type> FileConverters more type-safe by restricting it to values ​​that implement IApplicationPrinter? In other words, something like this is possible:

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

Update: I do not want to store instances for two reasons:

  • Each class can handle several different types of files (see string[] PrintableExtensions ). The dictionary stores extensions as keys. There is no utility in creating and storing multiple instances of partitions of the same class.
  • Each printer class uses the COM API and Office Interop to instantiate third-party applications. It is better that a new instance of each class be created for the print job when required, and that the garbage collector can subsequently clean it up.
+6
source share
4 answers

Directly - remember that the things you put into the dictionary are Type objects, not objects that implement IApplicationPrinter .

Probably the best option here is to check that every type that you add to the dictionary implements IApplicationPrinter , checking if type.GetInterface("IApplicationPrinter") null or not.

+2
source

I would do it a little differently:

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

This method will not store instances in the dictionary as you wish and will create a new instance with every call to GetPrinterFor . It is also more strongly typed, since the return type of Func<> must be IApplicationPrinter .

+3
source

If you used Dictionary<string, IApplicationPrinter> , this does not necessarily mean that you will need to have a separate instance for each string , some of them can use the same instance.

If you do not want to do this, you can store factories in a dictionary. A factory can be an object that implements an interface (something like IApplicationPrinterFactory ), or just a delegate that can create an object. In your case, it will be Dictionary<string, Func<IApplicationPrinter>> . Performing this method is completely safe. To add to the dictionary, you would do something like:

 FileConverters = new Dictionary<string, Func<IApplicationPrinter>>(); Func<IApplicationPrinter> printerFactory = () => new WordPrinter(); foreach (string ext in WordPrinter.PrintableExtensions) FileConverters.Add(ext, printerFactory); 

If you are sure you want Dictionary<string, Type> , there is no way to limit this so that all types implement IApplicationPrinter . What you can do is create your own dictionary that checks the type when adding. This does not make compile-time security, but makes it more secure.

 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 } 
+1
source

First you need to change your approach to accessing the array of extensions, because you cannot access the property without instantiating it. I would use a custom attribute to tell your dictionary type which extension each class supports. It will look like this:

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

Using an attribute allows you to restrict the type. There are two ways to archive.

  • Throw an exception in the type constructor (see this link )
  • Use an abstract class instead of an interface, this allows you to restrict the class using a protected attribute (at this point you need to create a base class to access it from your type dictionary), which can only be applied to disparate classes.

Now you can simply check to see if a particular class has a PrinterExtensions attribute and access its property, which retrieves all extensions or calls a method that directly registers all extensions.

0
source

Source: https://habr.com/ru/post/907097/


All Articles