// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using Microsoft.ClearScript.Util; namespace Microsoft.ClearScript { ///

/// Provides the base implementation for all script engines. /// public abstract class ScriptEngine : IDisposable { #region data private Type accessContext; private ScriptAccess defaultAccess; private bool enforceAnonymousTypeAccess; private bool exposeHostObjectStaticMembers; private DocumentSettings documentSettings; private readonly DocumentSettings defaultDocumentSettings = new DocumentSettings(); private static readonly IUniqueNameManager nameManager = new UniqueNameManager(); private static readonly object nullHostObjectProxy = new object(); [ThreadStatic] private static ScriptEngine currentEngine; #endregion #region constructors /// /// Initializes a new script engine instance. /// /// A name to associate with the instance. Currently this name is used only as a label in presentation contexts such as debugger user interfaces. [Obsolete("Use ScriptEngine(string name, string fileNameExtensions) instead.")] protected ScriptEngine(string name) : this(name, null) { } /// /// Initializes a new script engine instance with the specified list of supported file name extensions. /// /// A name to associate with the instance. Currently this name is used only as a label in presentation contexts such as debugger user interfaces. /// A semicolon-delimited list of supported file name extensions. protected ScriptEngine(string name, string fileNameExtensions) { Name = nameManager.GetUniqueName(name, GetType().GetRootName()); defaultDocumentSettings.FileNameExtensions = fileNameExtensions; extensionMethodTable = realExtensionMethodTable = new ExtensionMethodTable(); } #endregion #region public members /// /// Gets the name associated with the script engine instance. /// public string Name { get; } /// /// Gets the script engine that is invoking a host member on the current thread. /// /// /// If multiple script engines are invoking host members on the current thread, this /// property gets the one responsible for the most deeply nested invocation. If no script /// engines are invoking host members on the current thread, this property returns /// null. /// public static ScriptEngine Current => currentEngine; /// /// Gets the script engine's recommended file name extension for script files. /// public abstract string FileNameExtension { get; } /// /// Allows script code to access non-public host resources. /// /// /// By setting this property to a type you declare that script code running in the current /// script engine is to be treated as if it were part of that type's implementation. Doing /// so does not expose any host resources to script code, but it affects which host /// resources are importable and which members of exposed resources are accessible. /// public Type AccessContext { get => accessContext; set { accessContext = value; OnAccessSettingsChanged(); } } /// /// Gets or sets the default script access setting for all members of exposed objects. /// /// /// Use , , or /// their subclasses to override this property for individual types and members. Note that /// this property has no effect on the method binding algorithm. If a script-based call is /// bound to a method that is blocked by this property, it will be rejected even if an /// overload exists that could receive the call. /// public ScriptAccess DefaultAccess { get => defaultAccess; set { defaultAccess = value; OnAccessSettingsChanged(); } } /// /// Enables or disables access restrictions for anonymous types. /// /// /// Anonymous types are /// internal /// and therefore accessible only within the same assembly, but ClearScript 5.5.3 and /// earlier permitted access to the public properties of an object even if its type was /// internal. Newer versions strictly enforce , but because /// anonymous types are particularly useful for scripting, ClearScript by default continues /// to expose their properties to external contexts. To override this behavior and enable /// normal access restrictions for anonymous types, set this property to true. /// public bool EnforceAnonymousTypeAccess { get => enforceAnonymousTypeAccess; set { enforceAnonymousTypeAccess = value; OnAccessSettingsChanged(); } } /// /// Controls whether host objects provide access to the static members of their exposed types to script code. /// public bool ExposeHostObjectStaticMembers { get => exposeHostObjectStaticMembers; set { exposeHostObjectStaticMembers = value; OnAccessSettingsChanged(); } } /// /// Enables or disables extension method support. /// public bool DisableExtensionMethods { get => extensionMethodTable == emptyExtensionMethodTable; set { var newExtensionMethodTable = value ? emptyExtensionMethodTable : realExtensionMethodTable; if (newExtensionMethodTable != extensionMethodTable) { ScriptInvoke(() => { if (newExtensionMethodTable != extensionMethodTable) { extensionMethodTable = newExtensionMethodTable; bindCache.Clear(); OnAccessSettingsChanged(); } }); } } } /// /// Enables or disables script code formatting. /// /// /// When this property is set to true, the script engine may format script code /// before executing or compiling it. This is intended to facilitate interactive debugging. /// The formatting operation currently includes stripping leading and trailing blank lines /// and removing global indentation. /// public bool FormatCode { get; set; } /// /// Controls whether script code is permitted to use reflection. /// /// /// When this property is set to true, script code running in the current script /// engine is permitted to use reflection. This affects /// Object.GetType(), /// Exception.GetType(), /// Delegate.Method, /// and . /// By default, any attempt to invoke these members from script code results in an /// exception. /// public bool AllowReflection { get; set; } /// /// Enables or disables type restriction for field, property, and method return values. /// /// /// When this property is set to true, script code running in the current script /// engine has access to the runtime types of all exposed host resources, which by default /// are restricted to their declared types. The default behavior is a general requirement /// for correct method binding, so setting this property to true is not recommended. /// /// public bool DisableTypeRestriction { get; set; } /// /// Enables or disables type restriction for array and list elements retrieved by index. /// /// /// In ClearScript 5.4.4 and earlier, indexed array and list elements were exempt from type /// restriction. ClearScript 5.4.5 introduced a breaking change to correct this, but you can /// set this property to true to restore the exemption if you have older script code /// that depends on it. /// /// public bool DisableListIndexTypeRestriction { get; set; } /// /// Enables or disables null wrapping for field, property, and method return values. /// /// /// When this property is set to true, all field, property, and method return values /// are marshaled with full .NET type information even if they are null. Note that /// such values will always fail equality comparison with JavaScript's /// null, /// VBScript's /// Nothing, /// and other similar values. Instead, use or /// to perform such a comparison. /// /// /// public bool EnableNullResultWrapping { get; set; } /// /// Enables or disables floating point narrowing. /// /// /// When this property is set to true, no attempt is made to convert floating-point /// values imported from the script engine to the narrowest equivalent .NET representation. /// The default behavior is more likely to result in successful method binding in specific /// scenarios, so setting this property to true is not recommended. /// public bool DisableFloatNarrowing { get; set; } /// /// Enables or disables the use of reflection-based method binding as a fallback. /// /// /// When this property is set to true, the script engine attempts to use /// reflection-based method binding when the default method binding algorithm fails. This /// approach reduces type safety, but it may be useful for running legacy scripts that rely /// on the specific behavior of reflection-based method binding. /// public bool UseReflectionBindFallback { get; set; } /// /// Enables or disables automatic host variable tunneling for by-reference arguments to script functions and delegates. /// /// /// When this property is set to true, the script engine replaces by-reference /// arguments to script functions and delegates with host variables, allowing script code /// to simulate output arguments if the script language does not support them natively. /// /// public bool EnableAutoHostVariables { get; set; } /// /// Gets or sets the engine's undefined import value. /// /// /// Some script languages support one or more special non-null values that represent /// nonexistent, missing, unknown, or undefined data. When such a value is marshaled to the /// host, the script engine maps it to the value of this property. The default value is /// . /// public object UndefinedImportValue { get; set; } = Undefined.Value; /// /// Gets or sets a callback that can be used to halt script execution. /// /// /// During script execution the script engine periodically invokes this callback to /// determine whether it should continue. If the callback returns false, the script /// engine terminates script execution and throws an exception. /// public ContinuationCallback ContinuationCallback { get; set; } /// /// Allows the host to access script resources dynamically. /// /// /// The value of this property is an object that is bound to the script engine's root /// namespace. It dynamically supports properties and methods that correspond to global /// script objects and functions. /// public abstract dynamic Script { get; } /// /// Allows the host to access script resources. /// /// /// The value of this property is an object that is bound to the script engine's root /// namespace. It allows you to access global script resources via the /// class interface. Doing so is likely to perform better than /// dynamic access via . /// public abstract ScriptObject Global { get; } /// /// Gets or sets the script engine's document settings. /// public DocumentSettings DocumentSettings { get => documentSettings ?? defaultDocumentSettings; set => documentSettings = value; } /// /// Exposes a host object to script code. /// /// A name for the new global script item that will represent the object. /// The object to expose. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddHostObject(string itemName, object target) { AddHostObject(itemName, HostItemFlags.None, target); } /// /// Exposes a host object to script code with the specified options. /// /// A name for the new global script item that will represent the object. /// A value that selects options for the operation. /// The object to expose. /// /// /// Once a host object is exposed to script code, its members are accessible via the script /// language's native syntax for member access. The following table provides details about /// the mapping between host members and script-accessible properties and methods. /// /// /// /// /// Member Type /// Exposed As /// Remarks /// /// /// Constructor /// N/A /// /// To invoke a constructor from script code, call /// HostFunctions.newObj(T). /// /// /// /// Property/Field /// Property /// N/A /// /// /// Method /// Method /// /// Overloaded host methods are merged into a single script-callable method. At /// runtime the correct host method is selected based on the argument types. /// /// /// /// Generic Method /// Method /// /// The ClearScript library supports dynamic C#-like type inference when invoking /// generic methods. However, some methods require explicit type arguments. To call /// such a method from script code, you must place the required number of /// host type objects /// at the beginning of the argument list. Doing so for methods that do not require /// explicit type arguments is optional. /// /// /// /// Extension Method /// Method /// /// Extension methods are available if the type that implements them has been /// exposed in the current script engine. /// /// /// /// Indexer /// Property /// /// Indexers appear as properties named "Item" that accept one or more index values /// as arguments. In addition, objects that implement expose /// properties with numeric names that match their valid indices. This includes /// one-dimensional host arrays and other collections. Multidimensional host arrays /// do not expose functional indexers; you must use /// Array.GetValue /// and /// Array.SetValue /// instead. /// /// /// /// Event /// Property /// /// Events are exposed as read-only properties of type . /// /// /// /// /// public void AddHostObject(string itemName, HostItemFlags flags, object target) { MiscHelpers.VerifyNonNullArgument(target, nameof(target)); AddHostItem(itemName, flags, target); } /// /// Exposes a host object to script code with the specified type restriction. /// /// The type whose members are to be made accessible from script code. /// A name for the new global script item that will represent the object. /// The object to expose. /// /// /// This method can be used to restrict script access to the members of a particular /// interface or base class. /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddRestrictedHostObject(string itemName, T target) { AddRestrictedHostObject(itemName, HostItemFlags.None, target); } /// /// Exposes a host object to script code with the specified type restriction and options. /// /// The type whose members are to be made accessible from script code. /// A name for the new global script item that will represent the object. /// A value that selects options for the operation. /// The object to expose. /// /// /// This method can be used to restrict script access to the members of a particular /// interface or base class. /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddRestrictedHostObject(string itemName, HostItemFlags flags, T target) { AddHostItem(itemName, flags, HostItem.Wrap(this, target, typeof(T))); } /// /// Creates a COM/ActiveX object and exposes it to script code. The registered class is /// specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the object. /// The programmatic identifier (ProgID) of the registered class to instantiate. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMObject(string itemName, string progID) { AddCOMObject(itemName, HostItemFlags.None, progID); } /// /// Creates a COM/ActiveX object on the specified server and exposes it to script code. The /// registered class is specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the object. /// The programmatic identifier (ProgID) of the registered class to instantiate. /// The name of the server on which to create the object. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMObject(string itemName, string progID, string serverName) { AddCOMObject(itemName, HostItemFlags.None, progID, serverName); } /// /// Creates a COM/ActiveX object and exposes it to script code with the specified options. /// The registered class is specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the object. /// A value that selects options for the operation. /// The programmatic identifier (ProgID) of the registered class to instantiate. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMObject(string itemName, HostItemFlags flags, string progID) { AddCOMObject(itemName, flags, progID, null); } /// /// Creates a COM/ActiveX object on the specified server and exposes it to script code with /// the specified options. The registered class is specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the object. /// A value that selects options for the operation. /// The programmatic identifier (ProgID) of the registered class to instantiate. /// The name of the server on which to create the object. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMObject(string itemName, HostItemFlags flags, string progID, string serverName) { AddHostItem(itemName, flags, MiscHelpers.CreateCOMObject(progID, serverName)); } /// /// Creates a COM/ActiveX object and exposes it to script code. The registered class is /// specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the object. /// The class identifier (CLSID) of the registered class to instantiate. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMObject(string itemName, Guid clsid) { AddCOMObject(itemName, HostItemFlags.None, clsid); } /// /// Creates a COM/ActiveX object on the specified server and exposes it to script code. The /// registered class is specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the object. /// The class identifier (CLSID) of the registered class to instantiate. /// The name of the server on which to create the object. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMObject(string itemName, Guid clsid, string serverName) { AddCOMObject(itemName, HostItemFlags.None, clsid, serverName); } /// /// Creates a COM/ActiveX object and exposes it to script code with the specified options. /// The registered class is specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the object. /// A value that selects options for the operation. /// The class identifier (CLSID) of the registered class to instantiate. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMObject(string itemName, HostItemFlags flags, Guid clsid) { AddCOMObject(itemName, flags, clsid, null); } /// /// Creates a COM/ActiveX object on the specified server and exposes it to script code with /// the specified options. The registered class is specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the object. /// A value that selects options for the operation. /// The class identifier (CLSID) of the registered class to instantiate. /// The name of the server on which to create the object. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMObject(string itemName, HostItemFlags flags, Guid clsid, string serverName) { AddHostItem(itemName, flags, MiscHelpers.CreateCOMObject(clsid, serverName)); } /// /// Exposes a host type to script code with a default name. /// /// The type to expose. /// /// /// This method uses 's name for the new global script item that /// will represent it. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(Type type) { AddHostType(HostItemFlags.None, type); } /// /// Exposes a host type to script code with a default name and the specified options. /// /// A value that selects options for the operation. /// The type to expose. /// /// /// This method uses 's name for the new global script item that /// will represent it. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(HostItemFlags flags, Type type) { AddHostType(type.GetRootName(), flags, type); } /// /// Exposes a host type to script code. /// /// A name for the new global script item that will represent the type. /// The type to expose. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(string itemName, Type type) { AddHostType(itemName, HostItemFlags.None, type); } /// /// Exposes a host type to script code with the specified options. /// /// A name for the new global script item that will represent the type. /// A value that selects options for the operation. /// The type to expose. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(string itemName, HostItemFlags flags, Type type) { MiscHelpers.VerifyNonNullArgument(type, nameof(type)); AddHostItem(itemName, flags, HostType.Wrap(type)); } /// /// Exposes a host type to script code. The type is specified by name. /// /// A name for the new global script item that will represent the type. /// The fully qualified name of the type to expose. /// Optional generic type arguments. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(string itemName, string typeName, params Type[] typeArgs) { AddHostType(itemName, HostItemFlags.None, typeName, typeArgs); } /// /// Exposes a host type to script code with the specified options. The type is specified by name. /// /// A name for the new global script item that will represent the type. /// A value that selects options for the operation. /// The fully qualified name of the type to expose. /// Optional generic type arguments. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(string itemName, HostItemFlags flags, string typeName, params Type[] typeArgs) { AddHostItem(itemName, flags, TypeHelpers.ImportType(typeName, null, false, typeArgs)); } /// /// Exposes a host type to script code. The type is specified by type name and assembly name. /// /// A name for the new global script item that will represent the type. /// The fully qualified name of the type to expose. /// The name of the assembly that contains the type to expose. /// Optional generic type arguments. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(string itemName, string typeName, string assemblyName, params Type[] typeArgs) { AddHostType(itemName, HostItemFlags.None, typeName, assemblyName, typeArgs); } /// /// Exposes a host type to script code with the specified options. The type is specified by /// type name and assembly name. /// /// A name for the new global script item that will represent the type. /// A value that selects options for the operation. /// The fully qualified name of the type to expose. /// The name of the assembly that contains the type to expose. /// Optional generic type arguments. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(string itemName, HostItemFlags flags, string typeName, string assemblyName, params Type[] typeArgs) { AddHostItem(itemName, flags, TypeHelpers.ImportType(typeName, assemblyName, true, typeArgs)); } /// /// Exposes host types to script code. /// /// The types to expose. /// /// /// This method uses each specified type's name for the new global script item that will /// represent it. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostTypes(params Type[] types) { if (types != null) { foreach (var type in types) { if (type != null) { AddHostType(type); } } } } /// /// Imports a COM/ActiveX type and exposes it to script code. The registered class is /// specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the type. /// The programmatic identifier (ProgID) of the registered class to import. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMType(string itemName, string progID) { AddCOMType(itemName, HostItemFlags.None, progID); } /// /// Imports a COM/ActiveX type from the specified server and exposes it to script code. The /// registered class is specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the type. /// The programmatic identifier (ProgID) of the registered class to import. /// The name of the server from which to import the type. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMType(string itemName, string progID, string serverName) { AddCOMType(itemName, HostItemFlags.None, progID, serverName); } /// /// Imports a COM/ActiveX type and exposes it to script code with the specified options. /// The registered class is specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the type. /// A value that selects options for the operation. /// The programmatic identifier (ProgID) of the registered class to import. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMType(string itemName, HostItemFlags flags, string progID) { AddCOMType(itemName, flags, progID, null); } /// /// Imports a COM/ActiveX type from the specified server and exposes it to script code with /// the specified options. The registered class is specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the type. /// A value that selects options for the operation. /// The programmatic identifier (ProgID) of the registered class to import. /// The name of the server from which to import the type. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMType(string itemName, HostItemFlags flags, string progID, string serverName) { AddHostItem(itemName, flags, HostType.Wrap(MiscHelpers.GetCOMType(progID, serverName))); } /// /// Imports a COM/ActiveX type and exposes it to script code. The registered class is /// specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the type. /// The class identifier (CLSID) of the registered class to import. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMType(string itemName, Guid clsid) { AddCOMType(itemName, HostItemFlags.None, clsid); } /// /// Imports a COM/ActiveX type from the specified server and exposes it to script code. The /// registered class is specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the type. /// The class identifier (CLSID) of the registered class to import. /// The name of the server from which to import the type. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMType(string itemName, Guid clsid, string serverName) { AddCOMType(itemName, HostItemFlags.None, clsid, serverName); } /// /// Imports a COM/ActiveX type and exposes it to script code with the specified options. /// The registered class is specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the type. /// A value that selects options for the operation. /// The class identifier (CLSID) of the registered class to import. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMType(string itemName, HostItemFlags flags, Guid clsid) { AddCOMType(itemName, flags, clsid, null); } /// /// Imports a COM/ActiveX type from the specified server and exposes it to script code with /// the specified options. The registered class is specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the type. /// A value that selects options for the operation. /// The class identifier (CLSID) of the registered class to import. /// The name of the server from which to import the type. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMType(string itemName, HostItemFlags flags, Guid clsid, string serverName) { AddHostItem(itemName, flags, HostType.Wrap(MiscHelpers.GetCOMType(clsid, serverName))); } /// /// Executes script code. /// /// The script code to execute. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as a statement. /// /// /// If a debugger is attached, it will present the specified script code to the user as a /// document with an automatically selected name. This document will not be discarded /// after execution. /// /// public void Execute(string code) { Execute(null, code); } /// /// Executes script code with an associated document name. /// /// A document name for the script code. Currently this name is used only as a label in presentation contexts such as debugger user interfaces. /// The script code to execute. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as a statement. /// /// /// If a debugger is attached, it will present the specified script code to the user as a /// document with the specified name. This document will not be discarded after execution. /// /// public void Execute(string documentName, string code) { Execute(documentName, false, code); } /// /// Executes script code with an associated document name, optionally discarding the document after execution. /// /// A document name for the script code. Currently this name is used only as a label in presentation contexts such as debugger user interfaces. /// True to discard the script document after execution, false otherwise. /// The script code to execute. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as a statement. /// /// /// If a debugger is attached, it will present the specified script code to the user as a /// document with the specified name. Discarding this document removes it from view but /// has no effect on the script engine. Only Windows Script engines honor /// . /// /// public void Execute(string documentName, bool discard, string code) { Execute(new DocumentInfo(documentName) { Flags = discard ? DocumentFlags.IsTransient : DocumentFlags.None }, code); } /// /// Executes script code with the specified document meta-information. /// /// A structure containing meta-information for the script document. /// The script code to execute. /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as a statement. /// public void Execute(DocumentInfo documentInfo, string code) { Execute(documentInfo.MakeUnique(this), code, false); } /// /// Loads and executes a script document. /// /// A string specifying the document to be loaded and executed. /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets script code loaded from the specified document as a statement. /// public void ExecuteDocument(string specifier) { ExecuteDocument(specifier, null); } /// /// Loads and executes a document with the specified category. /// /// A string specifying the document to be loaded and executed. /// An optional category for the requested document. /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets script code loaded from the specified document as a statement. /// public void ExecuteDocument(string specifier, DocumentCategory category) { ExecuteDocument(specifier, category, null); } /// /// Loads and executes a document with the specified category and context callback. /// /// A string specifying the document to be loaded and executed. /// An optional category for the requested document. /// An optional context callback for the requested document. /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets script code loaded from the specified document as a statement. /// public void ExecuteDocument(string specifier, DocumentCategory category, DocumentContextCallback contextCallback) { MiscHelpers.VerifyNonBlankArgument(specifier, nameof(specifier), "Invalid document specifier"); var document = DocumentSettings.LoadDocument(null, specifier, category, contextCallback); Execute(document.Info, document.GetTextContents()); } /// /// Executes script code as a command. /// /// The script command to execute. /// The command output. /// /// This method is similar to but optimized for command /// consoles. The specified command must be limited to a single expression or statement. /// Script engines can override this method to customize command execution as well as the /// process of converting the result to a string for console output. /// public virtual string ExecuteCommand(string command) { var documentInfo = new DocumentInfo("Command") { Flags = DocumentFlags.IsTransient }; return GetCommandResultString(Evaluate(documentInfo.MakeUnique(this), command, false)); } /// /// Evaluates script code. /// /// The script code to evaluate. /// The result value. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as an expression. /// /// /// If a debugger is attached, it will present the specified script code to the user as a /// document with an automatically selected name. This document will be discarded after /// execution. /// /// /// For information about the types of result values that script code can return, see /// . /// /// public object Evaluate(string code) { return Evaluate(null, code); } /// /// Evaluates script code with an associated document name. /// /// A document name for the script code. Currently this name is used only as a label in presentation contexts such as debugger user interfaces. /// The script code to evaluate. /// The result value. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as an expression. /// /// /// If a debugger is attached, it will present the specified script code to the user as a /// document with the specified name. This document will be discarded after execution. /// /// /// For information about the types of result values that script code can return, see /// . /// /// public object Evaluate(string documentName, string code) { return Evaluate(documentName, true, code); } /// /// Evaluates script code with an associated document name, optionally discarding the document after execution. /// /// A document name for the script code. Currently this name is used only as a label in presentation contexts such as debugger user interfaces. /// True to discard the script document after execution, false otherwise. /// The script code to evaluate. /// The result value. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as an expression. /// /// /// If a debugger is attached, it will present the specified script code to the user as a /// document with the specified name. Discarding this document removes it from view but /// has no effect on the script engine. Only Windows Script engines honor /// . /// /// /// The following table summarizes the types of result values that script code can return. /// /// /// Type /// Returned As /// Remarks /// /// /// String /// System.String /// N/A /// /// /// Boolean /// System.Boolean /// N/A /// /// /// Number /// System.Int32 or System.Double /// /// Other numeric types are possible. The exact conversions between script and .NET /// numeric types are defined by the script engine. /// /// /// /// Null Reference /// null /// N/A /// /// /// Undefined /// /// /// This represents JavaScript's /// undefined, /// VBScript's /// Empty, /// etc. /// /// /// /// Void /// /// /// This is returned when script code forwards the result of a host method that returns no value. /// /// /// /// Host Object /// Native .NET type /// /// This includes all .NET types not mentioned above, including value types (enums, /// structs, etc.), and instances of all other classes. Script code can only create /// these objects by invoking a host method or constructor. They are returned to /// the host in their native .NET form. /// /// /// /// Script Object /// /// /// This includes all native script objects that have no .NET representation. C#'s /// dynamic /// keyword provides a convenient way to access them. /// /// /// /// Other /// Unspecified /// /// This includes host types and other ClearScript-specific objects intended for /// script code use only. It may also include language-specific values that the /// ClearScript library does not support. /// /// /// /// /// public object Evaluate(string documentName, bool discard, string code) { return Evaluate(new DocumentInfo(documentName) { Flags = discard ? DocumentFlags.IsTransient : DocumentFlags.None }, code); } /// /// Evaluates script code with the specified document meta-information. /// /// A structure containing meta-information for the script document. /// The script code to evaluate. /// The result value. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as an expression. /// /// /// For information about the types of result values that script code can return, see /// . /// /// public object Evaluate(DocumentInfo documentInfo, string code) { return Evaluate(documentInfo.MakeUnique(this, DocumentFlags.IsTransient), code, true); } /// /// Loads and evaluates a script document. /// /// A string specifying the document to be loaded and evaluated. /// The result value. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets script code loaded from the specified document as an expression. /// /// /// For information about the types of result values that script code can return, see /// . /// /// public object EvaluateDocument(string specifier) { return EvaluateDocument(specifier, null); } /// /// Loads and evaluates a document with the specified category. /// /// A string specifying the document to be loaded and evaluated. /// An optional category for the requested document. /// The result value. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets script code loaded from the specified document as an expression. /// /// /// For information about the types of result values that script code can return, see /// . /// /// public object EvaluateDocument(string specifier, DocumentCategory category) { return EvaluateDocument(specifier, category, null); } /// /// Loads and evaluates a document with the specified category and context callback. /// /// A string specifying the document to be loaded and evaluated. /// An optional category for the requested document. /// An optional context callback for the requested document. /// The result value. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets script code loaded from the specified document as an expression. /// /// /// For information about the types of result values that script code can return, see /// . /// /// public object EvaluateDocument(string specifier, DocumentCategory category, DocumentContextCallback contextCallback) { MiscHelpers.VerifyNonBlankArgument(specifier, nameof(specifier), "Invalid document specifier"); var document = DocumentSettings.LoadDocument(null, specifier, category, contextCallback); return Evaluate(document.Info, document.GetTextContents()); } /// /// Invokes a global function or procedure. /// /// The name of the global function or procedure to invoke. /// Optional invocation arguments. /// The return value if a function was invoked, an undefined value otherwise. public object Invoke(string funcName, params object[] args) { MiscHelpers.VerifyNonBlankArgument(funcName, nameof(funcName), "Invalid function name"); return ((IDynamic)Script).InvokeMethod(funcName, args ?? ArrayHelpers.GetEmptyArray()); } /// /// Gets a string representation of the script call stack. /// /// The script call stack formatted as a string. /// /// This method returns an empty string if the script engine is not executing script code. /// The stack trace text format is defined by the script engine. /// public abstract string GetStackTrace(); /// /// Interrupts script execution and causes the script engine to throw an exception. /// /// /// This method can be called safely from any thread. /// public abstract void Interrupt(); /// /// Performs garbage collection. /// /// True to perform exhaustive garbage collection, false to favor speed over completeness. public abstract void CollectGarbage(bool exhaustive); #endregion #region internal members internal abstract IUniqueNameManager DocumentNameManager { get; } internal virtual bool EnumerateInstanceMethods => true; internal virtual bool EnumerateExtensionMethods => EnumerateInstanceMethods; internal virtual bool UseCaseInsensitiveMemberBinding => false; internal abstract void AddHostItem(string itemName, HostItemFlags flags, object item); internal object PrepareResult(T result, ScriptMemberFlags flags, bool isListIndexResult) { return PrepareResult(result, typeof(T), flags, isListIndexResult); } internal virtual object PrepareResult(object result, Type type, ScriptMemberFlags flags, bool isListIndexResult) { var wrapNull = flags.HasFlag(ScriptMemberFlags.WrapNullResult) || EnableNullResultWrapping; if (wrapNull && (result == null)) { return HostObject.WrapResult(null, type, true); } if (!flags.HasFlag(ScriptMemberFlags.ExposeRuntimeType) && !DisableTypeRestriction && (!isListIndexResult || !DisableListIndexTypeRestriction)) { return HostObject.WrapResult(result, type, wrapNull); } return result; } internal abstract object MarshalToScript(object obj, HostItemFlags flags); internal object MarshalToScript(object obj) { var hostItem = obj as HostItem; return MarshalToScript(obj, hostItem?.Flags ?? HostItemFlags.None); } internal object[] MarshalToScript(object[] args) { return args.Select(MarshalToScript).ToArray(); } internal abstract object MarshalToHost(object obj, bool preserveHostTarget); internal object[] MarshalToHost(object[] args, bool preserveHostTargets) { return args.Select(arg => MarshalToHost(arg, preserveHostTargets)).ToArray(); } internal abstract object Execute(UniqueDocumentInfo documentInfo, string code, bool evaluate); internal abstract object ExecuteRaw(UniqueDocumentInfo documentInfo, string code, bool evaluate); internal object Evaluate(UniqueDocumentInfo documentInfo, string code, bool marshalResult) { var result = Execute(documentInfo, code, true); if (marshalResult) { result = MarshalToHost(result, false); } return result; } internal string GetCommandResultString(object result) { if (result is HostItem hostItem) { if (hostItem.Target is IHostVariable) { return result.ToString(); } } var marshaledResult = MarshalToHost(result, false); if (marshaledResult is VoidResult) { return null; } if (marshaledResult == null) { return "[null]"; } if (marshaledResult is Undefined) { return marshaledResult.ToString(); } if (marshaledResult is ScriptItem) { return "[ScriptObject]"; } return result.ToString(); } internal void RequestInterrupt() { // Some script engines don't support IActiveScript::InterruptScriptThread(). This // method provides an alternate mechanism based on IActiveScriptSiteInterruptPoll. var tempScriptFrame = CurrentScriptFrame; if (tempScriptFrame != null) { tempScriptFrame.InterruptRequested = true; } } internal void CheckReflection() { if (!AllowReflection) { throw new UnauthorizedAccessException("Use of reflection is prohibited in this script engine"); } } internal virtual void OnAccessSettingsChanged() { } #endregion #region host-side invocation internal virtual void HostInvoke(Action action) { action(); } internal virtual T HostInvoke(Func func) { return func(); } #endregion #region script-side invocation internal ScriptFrame CurrentScriptFrame { get; private set; } internal IDisposable CreateEngineScope() { return Scope.Create(() => MiscHelpers.Exchange(ref currentEngine, this), previousEngine => currentEngine = previousEngine); } internal virtual void ScriptInvoke(Action action) { using (CreateEngineScope()) { ScriptInvokeInternal(action); } } internal virtual T ScriptInvoke(Func func) { using (CreateEngineScope()) { return ScriptInvokeInternal(func); } } internal void ScriptInvokeInternal(Action action) { var previousScriptFrame = CurrentScriptFrame; CurrentScriptFrame = new ScriptFrame(); try { action(); } finally { CurrentScriptFrame = previousScriptFrame; } } internal T ScriptInvokeInternal(Func func) { var previousScriptFrame = CurrentScriptFrame; CurrentScriptFrame = new ScriptFrame(); try { return func(); } finally { CurrentScriptFrame = previousScriptFrame; } } internal void ThrowScriptError() { if (CurrentScriptFrame != null) { ThrowScriptError(CurrentScriptFrame.ScriptError); } } internal static void ThrowScriptError(IScriptEngineException scriptError) { if (scriptError != null) { if (scriptError is ScriptInterruptedException) { throw new ScriptInterruptedException(scriptError.EngineName, scriptError.Message, scriptError.ErrorDetails, scriptError.HResult, scriptError.IsFatal, scriptError.ExecutionStarted, scriptError.ScriptException, scriptError.InnerException); } throw new ScriptEngineException(scriptError.EngineName, scriptError.Message, scriptError.ErrorDetails, scriptError.HResult, scriptError.IsFatal, scriptError.ExecutionStarted, scriptError.ScriptException, scriptError.InnerException); } } #endregion #region synchronized invocation internal virtual void SyncInvoke(Action action) { action(); } internal virtual T SyncInvoke(Func func) { return func(); } #endregion #region enumeration settings internal object EnumerationSettingsToken { get; private set; } = new object(); internal void OnEnumerationSettingsChanged() { EnumerationSettingsToken = new object(); } #endregion #region extension method table private static readonly ExtensionMethodTable emptyExtensionMethodTable = new ExtensionMethodTable(); private readonly ExtensionMethodTable realExtensionMethodTable; private ExtensionMethodTable extensionMethodTable; internal void ProcessExtensionMethodType(Type type) { if (extensionMethodTable != emptyExtensionMethodTable) { if (extensionMethodTable.ProcessType(type, AccessContext, DefaultAccess)) { bindCache.Clear(); } } } internal ExtensionMethodSummary ExtensionMethodSummary => extensionMethodTable.Summary; internal void RebuildExtensionMethodSummary() { if (extensionMethodTable != emptyExtensionMethodTable) { extensionMethodTable.RebuildSummary(); } } #endregion #region bind cache private readonly Dictionary bindCache = new Dictionary(); internal void CacheBindResult(BindSignature signature, object result) { bindCache.Add(signature, result); } internal bool TryGetCachedBindResult(BindSignature signature, out object result) { return bindCache.TryGetValue(signature, out result); } #endregion #region host item cache private readonly ConditionalWeakTable> hostObjectHostItemCache = new ConditionalWeakTable>(); private readonly ConditionalWeakTable> hostTypeHostItemCache = new ConditionalWeakTable>(); internal HostItem GetOrCreateHostItem(HostTarget target, HostItemFlags flags, HostItem.CreateFunc createHostItem) { if (target is HostObject hostObject) { return GetOrCreateHostItemForHostObject(hostObject, hostObject.Target, flags, createHostItem); } if (target is HostType hostType) { return GetOrCreateHostItemForHostType(hostType, flags, createHostItem); } if (target is HostMethod hostMethod) { return GetOrCreateHostItemForHostObject(hostMethod, hostMethod, flags, createHostItem); } if (target is HostVariableBase hostVariable) { return GetOrCreateHostItemForHostObject(hostVariable, hostVariable, flags, createHostItem); } if (target is HostIndexedProperty hostIndexedProperty) { return GetOrCreateHostItemForHostObject(hostIndexedProperty, hostIndexedProperty, flags, createHostItem); } return CreateHostItem(target, flags, createHostItem, null); } private HostItem GetOrCreateHostItemForHostObject(HostTarget hostTarget, object target, HostItemFlags flags, HostItem.CreateFunc createHostItem) { var cacheEntry = hostObjectHostItemCache.GetOrCreateValue(target ?? nullHostObjectProxy); List activeWeakRefs = null; var staleWeakRefCount = 0; foreach (var weakRef in cacheEntry) { var hostItem = weakRef.Target as HostItem; if (hostItem == null) { staleWeakRefCount++; } else { if ((hostItem.Target.Type == hostTarget.Type) && (hostItem.Flags == flags)) { return hostItem; } if (activeWeakRefs == null) { activeWeakRefs = new List(cacheEntry.Count); } activeWeakRefs.Add(weakRef); } } if (staleWeakRefCount > 4) { cacheEntry.Clear(); if (activeWeakRefs != null) { cacheEntry.Capacity = activeWeakRefs.Count + 1; cacheEntry.AddRange(activeWeakRefs); } } return CreateHostItem(hostTarget, flags, createHostItem, cacheEntry); } private HostItem GetOrCreateHostItemForHostType(HostType hostType, HostItemFlags flags, HostItem.CreateFunc createHostItem) { if (hostType.Types.Length != 1) { return CreateHostItem(hostType, flags, createHostItem, null); } var cacheEntry = hostTypeHostItemCache.GetOrCreateValue(hostType.Types[0]); List activeWeakRefs = null; var staleWeakRefCount = 0; foreach (var weakRef in cacheEntry) { var hostItem = weakRef.Target as HostItem; if (hostItem == null) { staleWeakRefCount++; } else { if (hostItem.Flags == flags) { return hostItem; } if (activeWeakRefs == null) { activeWeakRefs = new List(cacheEntry.Count); } activeWeakRefs.Add(weakRef); } } if (staleWeakRefCount > 4) { cacheEntry.Clear(); if (activeWeakRefs != null) { cacheEntry.Capacity = activeWeakRefs.Count + 1; cacheEntry.AddRange(activeWeakRefs); } } return CreateHostItem(hostType, flags, createHostItem, cacheEntry); } private HostItem CreateHostItem(HostTarget hostTarget, HostItemFlags flags, HostItem.CreateFunc createHostItem, List cacheEntry) { var newHostItem = createHostItem(this, hostTarget, flags); if (cacheEntry != null) { cacheEntry.Add(new WeakReference(newHostItem)); } if (hostTarget.Target is IScriptableObject scriptableObject) { scriptableObject.OnExposedToScriptCode(this); } return newHostItem; } #endregion #region host item collateral internal abstract HostItemCollateral HostItemCollateral { get; } #endregion #region shared host target member data internal readonly HostTargetMemberData SharedHostMethodMemberData = new HostTargetMemberData(); internal readonly HostTargetMemberData SharedHostIndexedPropertyMemberData = new HostTargetMemberData(); internal readonly HostTargetMemberData SharedScriptMethodMemberData = new HostTargetMemberData(); private readonly ConditionalWeakTable> sharedHostObjectMemberDataCache = new ConditionalWeakTable>(); internal HostTargetMemberData GetSharedHostObjectMemberData(HostObject target, Type targetAccessContext, ScriptAccess targetDefaultAccess, HostTargetFlags targetFlags) { var cacheEntry = sharedHostObjectMemberDataCache.GetOrCreateValue(target.Type); List activeWeakRefs = null; var staleWeakRefCount = 0; foreach (var weakRef in cacheEntry) { var memberData = weakRef.Target as HostTargetMemberDataWithContext; if (memberData == null) { staleWeakRefCount++; } else { if ((memberData.AccessContext == targetAccessContext) && (memberData.DefaultAccess == targetDefaultAccess) && (memberData.TargetFlags == targetFlags)) { return memberData; } if (activeWeakRefs == null) { activeWeakRefs = new List(cacheEntry.Count); } activeWeakRefs.Add(weakRef); } } if (staleWeakRefCount > 4) { cacheEntry.Clear(); if (activeWeakRefs != null) { cacheEntry.Capacity = activeWeakRefs.Count + 1; cacheEntry.AddRange(activeWeakRefs); } } var newMemberData = new HostTargetMemberDataWithContext(targetAccessContext, targetDefaultAccess, targetFlags); cacheEntry.Add(new WeakReference(newMemberData)); return newMemberData; } #endregion #region event connections private readonly EventConnectionMap eventConnectionMap = new EventConnectionMap(); internal EventConnection CreateEventConnection(object source, EventInfo eventInfo, Delegate handler) { return eventConnectionMap.Create(this, source, eventInfo, handler); } internal void BreakEventConnection(IEventConnection connection) { eventConnectionMap.Break(connection); } private void BreakAllEventConnections() { eventConnectionMap.Dispose(); } #endregion #region disposal / finalization /// /// Releases all resources used by the script engine. /// /// /// Call Dispose() when you are finished using the script engine. Dispose() /// leaves the script engine in an unusable state. After calling Dispose(), you must /// release all references to the script engine so the garbage collector can reclaim the /// memory that the script engine was occupying. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the unmanaged resources used by the script engine and optionally releases the managed resources. /// /// True to release both managed and unmanaged resources; false to release only unmanaged resources. /// /// This method is called by the public method and the /// Finalize method. invokes the /// protected Dispose(Boolean) method with the /// parameter set to true. Finalize invokes /// Dispose(Boolean) with set to false. /// protected virtual void Dispose(bool disposing) { if (disposing) { BreakAllEventConnections(); } } /// /// Releases unmanaged resources and performs other cleanup operations before the script engine is reclaimed by garbage collection. /// /// /// This method overrides . Application code should not /// call this method; an object's Finalize() method is automatically invoked during /// garbage collection, unless finalization by the garbage collector has been disabled by a /// call to . /// ~ScriptEngine() { Dispose(false); } #endregion #region Nested type: ScriptFrame internal sealed class ScriptFrame { public Exception HostException { get; set; } public IScriptEngineException ScriptError { get; set; } public IScriptEngineException PendingScriptError { get; set; } public bool InterruptRequested { get; set; } } #endregion #region Nested type: EventConnectionMap private sealed class EventConnectionMap : IDisposable { private readonly HashSet map = new HashSet(); private readonly InterlockedOneWayFlag disposedFlag = new InterlockedOneWayFlag(); internal EventConnection Create(ScriptEngine engine, object source, EventInfo eventInfo, Delegate handler) { var connection = new EventConnection(engine, source, eventInfo, handler); if (!disposedFlag.IsSet) { lock (map) { map.Add(connection); } } return connection; } internal void Break(IEventConnection connection) { var mustBreak = true; if (!disposedFlag.IsSet) { lock (map) { mustBreak = map.Remove(connection); } } if (mustBreak) { connection.Break(); } } public void Dispose() { if (disposedFlag.Set()) { var connections = new List(); lock (map) { connections.AddRange(map); } connections.ForEach(connection => connection.Break()); } } } #endregion } }