-4

我正在为一个类编写扩展方法,并且想访问在 using 块中定义的 IDisposable 对象,该对象通常包含对扩展方法的调用。

我不想简单地将 IDisposable 传递给方法调用,这会降低我的 API 编程模型的简单性。完成我所追求的也将使代码更像我正在集成的第三方 API。

我可以想象一种解决方法:在某个全局位置注册 IDisposable,可能绑定到当前线程 ID,以便可以通过工厂方法调用或类似的东西在扩展方法中查找它。当退出 using 块并最终调用其 Dispose() 方法时,该对象可以取消注册自身(为了使这项工作我想我可能需要使用弱引用)。

这似乎不是很不干净,但对于我的口味来说,它有点过于迂回了。有没有更直接的方法可以做到这一点?

这是我想做的:

public static class ExtensionMethods { 

    public static void Foo(this Bar b) {
        // Access t to enable this extension method to do its work, whatever that may be
    }

}

public class Bar {

}

public class Schlemazel {

    public void DoSomething() {

        using (Thingamabob t = new Thingamabob()) {

            Bar b = new Bar();

            b.Foo();

        }

    }

}

编辑:

以下是使用弱引用和简单的基于线程的注册系统实现的解决方案。即使在合理的负载下,它似乎也能工作并且稳定,但当然,在一个真正过载的系统上,它理论上可能会由于锁争用而开始抛出错误。

我认为有人看到这个解决方案可能会很有趣,但同样,它引入了不必要的复杂性,我只愿意在必要时这样做。同样,目标是第三方 API 的干净扩展,我可以在第三方 API 创建的对象上调用扩展方法,其中扩展方法依赖于某些上下文,这些上下文对于每个小扩展都很难创建或获取方法调用。

我留下了一些控制台输出语句,因此如果您好奇,您实际上可以将这些类放入命令行项目并查看它们的全部运行情况。

public class Context : IDisposable
{
    private const int MAX_LOCK_TRIES = 3;
    private static TimeSpan MAX_WRITE_LOCK_TIMEOUT = TimeSpan.FromTicks(500);

    private static System.Threading.ReaderWriterLockSlim readerWriterLock = new System.Threading.ReaderWriterLockSlim();
    static IDictionary<string, WeakReference<Context>> threadContexts = new Dictionary<string, WeakReference<Context>>();

    private bool registered;

    private string threadID;
    private string ThreadID
    {
        get { return threadID; }
        set
        {
            if (threadID != null)
                throw new InvalidOperationException("Cannot associate this context with more than one thread");
            threadID = value;
        }
    }

    /// <summary>
    /// Constructs a Context suitable for use in a using() statement
    /// </summary>
    /// <returns>A Context which will automatically deregister itself when it goes out of scope, i.e. at the end of a using block</returns>
    public static Context CreateContext()
    {
        Console.WriteLine("CreateContext()");

        return new Context(true);
    }

    private Context(bool register)
    {
        if (register)
        {

        registered = true;
        try
        {
            RegisterContext(this);
        }
        catch
        {
            registered = false;
        }
    }
    else
        registered = false;
}

public Context()
{
    registered = false;
}

public void Process(ThirdPartyObject o, params string[] arguments)
{
    Console.WriteLine("Context.Process(o)");

    // Process o, sometimes using the third-party API which this object has access to
    // This hides away the complexity of accessing that API, including obviating the need
    // to reconstruct and configure heavyweight objects to access it; calling code can 
    // blithely call useful methods on individual objects without knowing the messy details
}

public void Dispose()
{
    if (registered) 
        DeregisterContext(this);
}

private static void RegisterContext(Context c)
{
    if (c == null)
        throw new ArgumentNullException();

    c.ThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId.ToString();

    Console.WriteLine("RegisterContext() " + c.ThreadID);

    bool lockEntered = false;
    int tryCount = 0;

    try
    {
        while (!readerWriterLock.TryEnterWriteLock(TimeSpan.FromTicks(5000)))
            if (++tryCount > MAX_LOCK_TRIES)
                throw new OperationCanceledException("Cannot register context (timeout)");

        lockEntered = true;

        threadContexts[c.ThreadID] = new WeakReference<Context>(c);
    }
    finally
    {
        if (lockEntered)
            readerWriterLock.ExitWriteLock();
    }
}

private static void DeregisterContext(Context c)
{
    if (c == null)
        throw new ArgumentNullException();
    else if (!c.registered)
        return;

    Console.WriteLine("DeregisterContext() " + c.ThreadID);

    bool lockEntered = false;
    int tryCount = 0;

    try
    {
        while (!readerWriterLock.TryEnterWriteLock(TimeSpan.FromTicks(5000)))
            if (++tryCount > MAX_LOCK_TRIES)
                throw new OperationCanceledException("Cannot deregister context (timeout)");

        lockEntered = true;

        if (threadContexts.ContainsKey(c.ThreadID)) 
        {
            Context registeredContext = null;

            if (threadContexts[c.ThreadID].TryGetTarget(out registeredContext))
            {
                if (registeredContext == c)
                {
                    threadContexts.Remove(c.ThreadID);
                }
            }
            else
                threadContexts.Remove(c.ThreadID);
        }
    }
    finally
    {
        if (lockEntered)
            readerWriterLock.ExitWriteLock();
    }
}

/// <summary>
/// Gets the Context for this thread, if one has been registered
/// </summary>
/// <returns>The Context for this thread, which would generally be defined in a using block using Context.CreateContext()</returns>
internal static Context GetThreadContext()
{
    string threadID = System.Threading.Thread.CurrentThread.ManagedThreadId.ToString();

    Console.WriteLine("GetThreadContext() " + threadID);

    bool lockEntered = false;
    int tryCount = 0;

    try
    {
        while (!readerWriterLock.TryEnterReadLock(TimeSpan.FromTicks(5000)))
            if (++tryCount > MAX_LOCK_TRIES)
                throw new OperationCanceledException("Cannot get context (timeout)");

        lockEntered = true;
        Context registeredContext = null;

        if (threadContexts.ContainsKey(threadID))
            threadContexts[threadID].TryGetTarget(out registeredContext);

        return registeredContext;
    }
    finally
    {
        if (lockEntered)
            readerWriterLock.ExitReadLock();
    }

    }
}

// Imagine this is some third-party API
public static class ThirdPartyApi
{
    // Imagine this is any call to the third-party API that returns an object from that API which we'd like to decorate with an extension method
    public static ThirdPartyObject GetThirdPartyObject()
    {
        return new ThirdPartyObject();
    }
}

// Imagine this is some class from a third-party API, to which we would like to add extension methods
public class ThirdPartyObject
{
    internal ThirdPartyObject() { }
}

public static class ExtensionMethods
{
    public static void DoSomething(this ThirdPartyObject o) {
        // get the object I need to access resources to do my work

        Console.WriteLine("o.DoSomething()");

        Context c = Context.GetThreadContext();

        c.Process(o);
    }
}

你可以很简单地测试它,使用如下代码:

    ThirdPartyObject o;

    using (Context.CreateContext())
    {
        o = ThirdPartyApi.GetThirdPartyObject(); // or a call to my own code to get it, encapsulating calls to the third-party API

        // Call the method we've tacked on to the third party API item
        o.DoSomething();
    }

    try
    {
        // If the registered context has been disposed/deregistered, this will throw an error;
        // there is of course no way of knowing when it will happen, but in my simple testing
        // even this first attempt always throws an error, on my relatively unburdened system.
        // This means that with this model, one should not access the using-block Context
        // outside of the using block, but that's of course true in general of using statements
        o.DoSomething();
    }
    catch (Exception e)
    {
        Console.WriteLine(e.ToString());
    }

    System.Threading.Thread.Sleep(1000);

    try
    {
        // Should almost certainly see an error now
        o.DoSomething();
    }
    catch (Exception e)
    {
        Console.WriteLine(e.ToString());
    }
4

1 回答 1

2

t变量传递给扩展方法。

public static class ExtensionMethods { 
    public static void Foo(this Bar b, Thingamabob t) {
        // Access t to enable this extension method to do its work, whatever that may be
    }
}

public class Bar { }

public class Schlemazel {
    public void DoSomething() {
        using (Thingamabob t = new Thingamabob()) {
            Bar b = new Bar();
            b.Foo(t);
        }
    }
}
于 2013-06-22T01:10:38.667 回答