2013-03-22 32 views
9

Tôi đã đọc một số câu hỏi và câu trả lời của Stack Overflow, cùng với một số bài đăng trên blog (bao gồm khởi tạo đơn giản của Jon Skeet), và tất cả dường như đều tập trung vào việc khởi tạo càng lười càng tốt. Có vẻ như về cơ bản có hai tùy chọn để khởi tạo tĩnh:Eager static constructor

  • Tại tham chiếu đầu tiên một thể hiện hoặc thành viên tĩnh của một lớp
  • Tại một thời gian không xác định giữa sự bắt đầu của chương trình và tài liệu tham khảo đầu tiên.

Có cách nào để có được một constructor tĩnh (hoặc một số hình thức mã khởi tạo) để chạy cho một lớp học đặc biệt (hoặc lớp) vào lúc bắt đầu của chương trình?

Bối cảnh: Thư viện của chúng tôi sẽ phân tích cú pháp XML đến và trả về đối tượng. Loại đối tượng được trả về phụ thuộc vào phần tử XML được phân tích cú pháp. Chúng tôi cung cấp hai lớp đơn giản: một là một lớp rất cơ bản cho phép truy cập vào các attribues và XML bên trong (như một chuỗi), không có tính năng; thứ hai là cho một loại đối tượng cụ thể, và cung cấp kiểm tra ràng buộc và các tên cụ thể theo ngữ cảnh hơn để truy cập/chỉnh sửa các giá trị.

Trình phân tích cú pháp xác định cách phân tích cú pháp một phần tử XML cụ thể bằng cách xem xét danh sách các trình phân tích cú pháp của nó. Nếu nó có một trình phân tích cú pháp cho phần tử nó phân tích cú pháp (được xác định theo tên), nó sử dụng nó. Nếu không, hoặc nếu nó không thành công, nó sẽ rơi trở lại trên trình phân tích cú pháp cơ bản.

Nhà phát triển sử dụng thư viện của chúng tôi rất có khả năng viết các lớp học riêng của họ cho các phần tử XML cụ thể. Thay vì yêu cầu họ thêm phương thức phân tích cú pháp của từng lớp vào danh sách ở đầu mỗi ứng dụng, sẽ rất tuyệt nếu mỗi lớp có thể có một hàm tạo tĩnh để thêm trình phân tích cú pháp của chính nó vào danh sách, dự án sẽ đăng ký nó. Tuy nhiên, các nhà xây dựng tĩnh sẽ không kích hoạt cho đến khi lớp thực sự được tham chiếu và chúng tôi không đảm bảo rằng mọi lớp như vậy sẽ được tham chiếu trước khi bắt đầu phân tích cú pháp.

Có cách nào để đảm bảo một số vụ khởi tạo cho mỗi lớp trong lúc bắt đầu ứng dụng không? Lợi ích của việc này chỉ đơn giản là bao gồm các lớp trong dự án và không phải thêm từng phương thức phân tích cú pháp vào danh sách của trình phân tích cú pháp của chúng tôi khi chạy, đây là một tiện ích khá nhỏ, vì vậy để có được giá trị công việc, giải pháp cần khá đơn giản và dễ thực hiện.

+0

tôi nghĩ rằng những gợi ý dưới đây của phản ánh trên lớp phân biệt (ví dụ như bởi các thuộc tính, lớp cơ sở hoặc giao diện) là hứa hẹn nhất . Bạn có thể đặt mã phản chiếu một số hàm tạo tĩnh của một lớp mà _surely_ được khởi tạo. – Virtlink

Trả lời

13

Có cách nào để có được một hàm tạo tĩnh (hoặc một số dạng mã khởi tạo) để chạy cho một lớp (hoặc các lớp) cụ thể ở đầu chương trình không?

Có vẻ như bạn muốn một số loại "bộ khởi tạo mô-đun hoặc lắp ráp". Tôi không nghĩ rằng một điều như vậy tồn tại trong IL (mặc dù tôi có thể sai) và nó chắc chắn không tồn tại trong C#.

Bạn luôn có thể tạo một số loại thuộc tính và sau đó sử dụng sự phản chiếu để tìm tất cả các loại được trang trí với thuộc tính đó và khởi tạo chúng một cách rõ ràng. (Lưu ý rằng nó trở nên phức tạp hơn với các loại chung chung ... bạn có thể muốn giới hạn nó thành những cái không chung chung.)

EDIT: Tôi đã tìm thấy một cặp vợ chồng thêm nhiều lựa chọn:

CHỈNH SỬA: Với nhiều ngữ cảnh hơn, tôi nghi ngờ bất kỳ cách chữa nào cũng sẽ tệ hơn bệnh. Bất kỳ nhà phát triển nào muốn viết dựa trên sự phản chiếu "tìm tất cả các trình phân tích cú pháp với thuộc tính này" (hoặc tương tự) đều không có nhiều việc phải làm, nhưng tôi không nghĩ bạn muốn can thiệp vào khởi động ứng dụng của riêng họ.

Để làm cho cuộc sống của người khác dễ dàng hơn mà không áp đặt bất cứ điều gì, bạn luôn có thể bao gồm mà phản ánh một phần bản thân:

public static void RegisterAllParsers(Assembly assembly) 

... mà có lẽ sẽ là thuộc tính dựa trên. Tất nhiên, nó chỉ có thể nhận được phương pháp phân tích cú pháp tĩnh tĩnh - nếu bất kỳ nhà phát triển nào có thể phân tích theo các cách khác nhau tùy thuộc vào việc khởi tạo nhà máy, bạn không thể dễ dàng đăng ký tự động.

Nhà phát triển sau đó sẽ cần phải gọi:

LibraryClass.RegisterAllParsers(typeof(SomeTypeInProgram).Assembly); 

khi khởi động. Đó có lẽ không quá khó để nhớ - và hầu hết các ứng dụng chỉ có một điểm vào duy nhất, hoặc ít nhất là một số mã khởi động chung.

+0

"mô-đun hoặc lắp ráp khởi" âm thanh khá chính xác. Về cơ bản chúng tôi muốn có một vài lớp để xử lý một số khởi tạo của riêng mình, thay vì các nhà phát triển sử dụng thư viện phải khởi tạo chúng bằng tay trong mọi ứng dụng. Đó là một vấn đề thuận tiện/đơn giản, vì vậy nếu nó là phức tạp khủng khiếp như bạn đề nghị, nó có lẽ không phải là giá trị nó. – yoozer8

+0

Và phương pháp scan-the-assembly-looking-for-the-attribute sẽ tốn kém vì (1) nó liên quan đến sự phản chiếu và (2) nó sẽ quét * mọi loại trong assembly *. – cdhowie

+0

@Jim: Tôi đã tìm thấy một vài tùy chọn khác cho bạn - xem chỉnh sửa của tôi. –

2

Afaik không có cách nào để làm điều đó một cách rõ ràng, nhưng bạn có thể tạo ra một cái gì đó như sau (tôi cảnh báo bạn bây giờ, xấu xí và không nó nhanh):

[System.AttributeUsage(System.AttributeTargets.Class | 
        System.AttributeTargets.Struct)] 
public class AppInitialized : System.Attribute 
{ 
    private MethodInfo _mInfo; 

    public AppInitialized(Type t, String method) 
    { 
     _mInfo = t.GetMethod(method, BindingFlags.Static | BindingFlags.Public); 
    } 

    public void Initialize() 
    { 
     if (_mInfo != null) 
      _mInfo.Invoke(null, null); 
    } 
} 

[AppInitialized(typeof(InitializeMe), "Initialize")] 
public class InitializeMe 
{ 
    public static void Initialize() 
    { 
     Console.WriteLine("InitializeMe initialized"); 
    } 
} 

Và sau đó khi tải ứng dụng của bạn, sử dụng một cái gì đó như thế này để khởi tạo tất cả mọi thứ với các thuộc tính tùy chỉnh:

foreach (Type type in Assembly.GetExecutingAssembly().GetTypes()) 
{ 
    var a = Attribute.GetCustomAttribute(type, typeof(AppInitialized), true) 
     as AppInitialized; 
    if (a != null) 
     a.Initialize(); 
} 
2

Một chút giống như @FlyingStreudel, tôi cũng đã gom góp một cái gì đó "ôn hòa" làm những gì bạn đang sau:

Các thuộc tính:

[AttributeUsage(AttributeTargets.All)] 
public class ModuleInitializerAttribute : Attribute 
{ 
    private readonly string _assemblyName; 
    private readonly Func<Module, bool> _modulePredicate; 

    private readonly string _typeName; 
    private readonly string _methodName; 

    /// <summary> 
    /// Only used in my test rig so I can make sure this assembly is loaded 
    /// </summary> 
    public static void CallMe() {} 

    public ModuleInitializerAttribute(string assemblyName, string moduleName, string typeWithMethod, string methodToInvoke) 
    { 
     _assemblyName = assemblyName; 
     _modulePredicate = mod => moduleName == null || mod.Name.Equals(moduleName, StringComparison.OrdinalIgnoreCase); 
     _typeName = typeWithMethod; 
     _methodName = methodToInvoke; 

     AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad; 
     AppDomain.CurrentDomain.DomainUnload += AppDomainUnloading; 

     CheckLoadedAssemblies(); 
    } 

    private void CheckLoadedAssemblies() 
    { 
     AppDomain.CurrentDomain.GetAssemblies().ToList().ForEach(this.CheckAssembly); 
    } 

    private void AppDomainUnloading(object sender, EventArgs e) 
    { 
     // Unwire ourselves 
     AppDomain.CurrentDomain.AssemblyLoad -= this.OnAssemblyLoad; 
     AppDomain.CurrentDomain.DomainUnload -= AppDomainUnloading; 
    } 

    private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args) 
    { 
     CheckAssembly(args.LoadedAssembly); 
    } 

    private void CheckAssembly(Assembly asm) 
    { 
     if (asm.FullName == _assemblyName) 
     { 
      var module = asm.GetModules().FirstOrDefault(_modulePredicate); 
      if (module != null) 
      { 
       var type = module.GetType(string.Concat(asm.GetName().Name, ".", _typeName)); 
       if (type != null) 
       { 
        var method = type.GetMethod(_methodName); 
        if (method != null) 
        { 
         method.Invoke(null, null); 
        } 
       } 
      } 
     } 
    } 

} 

Các Testing giàn:

class Program 
{ 
    [ModuleInitializer("ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "ClassLibrary1.dll", "ModuleInitializerTest", "ModuleInitialize")] 
    static void Main(string[] args) 
    { 
     Console.WriteLine("Loaded assemblies:"); 
     var asms = AppDomain.CurrentDomain.GetAssemblies(); 
     foreach (var assembly in asms) 
     { 
      Console.WriteLine("\tAssembly Name:{0}", assembly.GetName()); 
      var mods = assembly.GetModules(); 
      foreach (var module in mods) 
      { 
       Console.WriteLine("\t\tModule Name:{0}", module.Name); 
      } 
     } 
     // This should trigger the load of the ClassLibrary1 assembly 
     aReference(); 
     Console.ReadLine(); 
    } 

    static void aReference() 
    { 
     var foo = new SomeOtherClass();   
    } 

} 

Và lớp lib khác:

namespace ClassLibrary1 
{ 
    public class SomeOtherClass 
    { 

    } 

    public static class ModuleInitializerTest 
    { 
     public static void ModuleInitialize() 
     { 
      // Do interesting stuff here? 
     } 
    } 
} 
1

tôi khuyên bạn nên xem xét việc sử dụng các Managed Extensibility Framework (MEF) cho điều này (System.ComponentModel.Composition namespace) . Khách hàng của bạn có thể chỉ cần thêm thuộc tính [Export(typeof(ISomeParserInterface))] và MEF sẽ có thể cung cấp cho trình phân tích cú pháp của bạn tất cả các tiện ích có sẵn.

Bạn thậm chí có thể sử dụng ExportMetadataAttribute để cho phép mã của bạn chỉ tạo nhanh các trình phân tích cú pháp mà thực sự cần cho các yếu tố mà nó gặp phải.

[Export(typeof(ISomeParserInterface))] 
[ExportMetadata("ElementName", "SomeXmlElement")] 
0

Bạn có thể xác định trình phân tích cú pháp nào sẽ sử dụng cho phần tử XML cụ thể dựa trên ngữ cảnh phân tích cú pháp XML hiện tại. Mọi đối tượng CLR sẽ được phân tích cú pháp từ XML sẽ được chứa trong một số đối tượng CLR khác làm thành viên của nó (trường hoặc thuộc tính) ngoại trừ đối tượng gốc. Vì vậy, trình phân tích cú pháp XML có thể được xác định bởi loại thành viên (loại trường hoặc loại thuộc tính). Đối với đối tượng CLR gốc, mà XML phải được phân tích cú pháp thành, loại phải được xác định rõ ràng.

Đây là còn C# mã mẫu: Dự án

XmlParserLibrary:

using System; 
using System.Collections.Generic; 
using System.Xml; 

namespace XmlParserLibrary 
{ 
    public sealed class XmlParser 
    { 
     private readonly IDictionary<Type, IXmlParser> parsers = new Dictionary<Type, IXmlParser>() 
     { 
      { typeof(string), new StringXmlParser() } 
     }; 

     public T Parse<T>(XmlReader reader) 
     { 
      return (T)this.Parse(reader, typeof(T)); 
     } 

     public object Parse(XmlReader reader, Type type) 
     { 
      // Position on element. 
      while (reader.Read() && reader.NodeType != XmlNodeType.Element) ; 

      return GetParser(type).Parse(reader); 
     } 

     private IXmlParser GetParser(Type type) 
     { 
      IXmlParser xmlParser; 
      if (!this.parsers.TryGetValue(type, out xmlParser)) 
       this.parsers.Add(type, xmlParser = this.CreateParser(type)); 

      return xmlParser; 
     } 

     private IXmlParser CreateParser(Type type) 
     { 
      var xmlParserAttribute = Attribute.GetCustomAttribute(type, typeof(XmlParserAttribute)) as XmlParserAttribute; 
      return xmlParserAttribute != null ? Activator.CreateInstance(xmlParserAttribute.XmlParserType) as IXmlParser : new FallbackXmlParser(this, type); 
     } 
    } 

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] 
    public sealed class XmlParserAttribute : Attribute 
    { 
     public Type XmlParserType { get; private set; } 

     public XmlParserAttribute(Type xmlParserType) 
     { 
      this.XmlParserType = xmlParserType; 
     } 
    } 

    public interface IXmlParser 
    { 
     object Parse(XmlReader reader); 
    } 

    internal sealed class StringXmlParser : IXmlParser 
    { 
     public object Parse(XmlReader reader) 
     { 
      return reader.ReadElementContentAsString(); 
     } 
    } 

    internal sealed class FallbackXmlParser : IXmlParser 
    { 
     private readonly XmlParser xmlParser; 
     private readonly Type type; 

     public FallbackXmlParser(XmlParser xmlParser, Type type) 
     { 
      this.xmlParser = xmlParser; 
      this.type = type; 
     } 

     public object Parse(XmlReader reader) 
     { 
      var item = Activator.CreateInstance(this.type); 

      while (reader.Read()) 
       switch (reader.NodeType) 
       { 
        case XmlNodeType.Element: 
         var propertyInfo = this.type.GetProperty(reader.LocalName); 
         var propertyValue = this.xmlParser.Parse(reader.ReadSubtree(), propertyInfo.PropertyType); 
         propertyInfo.SetValue(item, propertyValue, null); 
         break; 
       } 

      return item; 
     } 
    } 
} 

dự án XmlParserLibraryTest:

using System.Xml; 
using XmlParserLibrary; 

namespace XmlParserLibraryTest 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var xmlParser = new XmlParser(); 

      Letter letter; 
      using (var reader = XmlReader.Create("Letter.xml")) 
       letter = xmlParser.Parse<Letter>(reader); 
     } 
    } 

    public class Letter 
    { 
     public LetterAssociate Sender { get; set; } 
     public LetterAssociate Receiver { get; set; } 
     public LetterContent Content { get; set; } 
    } 

    public class LetterAssociate 
    { 
     public string Name { get; set; } 
     public string Address { get; set; } 
    } 

    [XmlParser(typeof(LetterContentXmlParser))] 
    public class LetterContent 
    { 
     public string Header { get; set; } 
     public string Body { get; set; } 
    } 

    internal class LetterContentXmlParser : IXmlParser 
    { 
     public object Parse(XmlReader reader) 
     { 
      var content = new LetterContent(); 

      while (reader.Read()) 
       switch (reader.NodeType) 
       { 
        case XmlNodeType.Element: 
         switch (reader.LocalName) 
         { 
          case "Header": 
           content.Header = reader.ReadElementContentAsString(); 
           break; 
          case "Body": 
           content.Body = reader.ReadElementContentAsString(); 
           break; 
         } 
         break; 
       } 

      return content; 
     } 
    } 
} 

Letter.xml file:

<?xml version="1.0" encoding="utf-8" ?> 
<Letter> 
    <Sender> 
    <Name>Sender name</Name> 
    <Address>Sender address</Address> 
    </Sender> 
    <Receiver> 
    <Name>Receiver name</Name> 
    <Address>Receiver address</Address> 
    </Receiver> 
    <Content> 
    <Header>This is letter header.</Header> 
    <Body>This is letter body.</Body> 
    </Content> 
</Letter> 

Điều này tương tự như cách làm thế nào XmlSerializer hoạt động, ngoại trừ nó không sử dụng trực tiếp khi phân tích cú pháp nhưng trước khi phân tích cú pháp để tạo tất cả các trình phân tích trong một phiên bản tạm thời riêng biệt (trước .NET 4.5). XmlSerializer cũng sẽ chăm sóc của rất nhiều thứ khác, ví dụ:

  • phân cấp lớp (khi loại tài sản là loại cơ sở nhưng đánh máy thực tế đăng vào XML là một số loại có nguồn gốc
  • loại bộ sưu tập
  • tôn trọng XML serialization related attributes từ System.Xml.Serialization namespace mà kiểm soát XML serialization/deserialization.