使用反射和锁定正确实例化分配给私有静态易失性变量的类

发布于 2024-12-12 04:47:03 字数 3869 浏览 0 评论 0原文

这是我想要改进或确认的一个人为示例。

我正在使用 (my/i)BATIS.NET(一个轻量级 ORM/数据映射器框架),我得到的是一个类,其中包含对数据库的每个表映射器的静态引用。它工作得很好,但是有太多重复,我认为可能有机会大大简化代码。该类目前看起来像这样:

public sealed class MyRepository
{
    private static string _connectionString;

    private volatile static TableAbcMapper _tableAbcMapper;
    private volatile static TableXyzMapper _tableXyzMapper;
    // and about 30 more of these

    private MyRepository()
    {
    }

    public static void Init(string connectionString)
    {
        _connectionString = connectionString;
    }

    public static string ConnectionString
    {
        get { return _connectionString; }
    }

    public static TableAbcMapper TableAbc
    {
        get
        {
            if (_tableAbcMapper == null)
            {
                lock (typeof(TableAbcMapper))
                {
                    if (_tableAbcMapper == null)
                    {
                        _tableAbcMapper = new TableAbcMapper(_connectionString);
                    }
                }
            }
            return _tableAbcMapper;
        }
    }

    public static TableXyzMapper TableXyz
    {
        get
        {
            if (_tableXyzMapper == null)
            {
                lock (typeof(TableXyzMapper))
                {
                    if (_tableXyzMapper == null)
                    {
                        _tableXyzMapper = new TableXyzMapper(_connectionString);
                    }
                }
            }
            return _tableXyzMapper;
        }
    }

    // and about 30 more of these readonly properties
}

每次我向数据库添加或删除表时,我都会向 MyRepository 添加一个 private 易失性静态 字段和那个又大又丑陋的 singleton-y 属性。代码>类。我的第一个想法是让属性调用类中的通用实例化函数;看起来像这样的东西:

private static void InitMapper<TMapper>(TMapper instance) where TMapper : MyMapper
{
    lock (typeof(TMapper))
    {
        if (instance == null)
        {
            instance = Activator.CreateInstance(typeof(TMapper), 
                new object[] { _connectionString }) as TMapper;
        }
    }
}

那么公共 getter 可以稍微简化为:

public static TableXyzMapper TableXyz
{
    get
    {
        if (_tableXyzMapper == null)
        {
            InitMapper<TableXyzMapper>(_tableXyzMapper);
        }
        return _tableXyzMapper;
    }
}

但是,我不知道传递易失性字段是否是一个好主意,并使用 refout 是一个禁忌,而且最重要的是它不会减少太多代码量。

我想做的是完全重构 MyRepository 类,使其没有私有字段和公共 getter,并使用反射立即初始化所有映射器,而不是延迟加载它们。我不必更改任何使用 MyRepository 类的代码,因为它看起来完全相同,但在幕后它会有点不同:

public sealed class MyRepository
{
    private MyRepository()
    {
    }

    public volatile static TableAbcMapper TableAbc = null;
    public volatile static TableXyzMapper TableXyz = null;

    public static void Init(string connectionString)
    {
        foreach (var fieldInfo in typeof(MyRepository).GetFields(BindingFlags.Static))
        {
            if (fieldInfo.GetValue(new MyRepository()) == null)
            {
                lock (fieldInfo.FieldType)
                {
                    if (fieldInfo.GetValue(new MyRepository()) == null)
                    {
                        fieldInfo.SetValue(new MyRepository(), 
                            fieldInfo.FieldType.GetConstructor(new Type[] { typeof(string) })
                                .Invoke(new object[] { connectionString }));
                    }
                }
            }
        }
    }
}

现在我唯一的维护将新表添加到数据库时要做的就是为其添加一个新的 public volatile static 字段,反射将处理其余的事情。

我对这种方法有几个问题:

  • 这种方法在功能上与原始类等效吗?
  • 使用反射定义易失性变量有危险吗?
  • 它和原始类一样可读吗(假设它全部被注释)?

最后,如果这是一个更适合代码审查网站的问题,我完全赞成将其迁移(mods!)。

So here's a contrived example of what I want to have improved or confirmed.

I'm using (my/i)BATIS.NET (a light-weight ORM/data mapper framework), and what I've got is a class with a static reference to each of the table mappers for a database. It works great, but there's so much repetition that I thought there might be an opportunity to greatly simplify the code. The class currently looks like so:

public sealed class MyRepository
{
    private static string _connectionString;

    private volatile static TableAbcMapper _tableAbcMapper;
    private volatile static TableXyzMapper _tableXyzMapper;
    // and about 30 more of these

    private MyRepository()
    {
    }

    public static void Init(string connectionString)
    {
        _connectionString = connectionString;
    }

    public static string ConnectionString
    {
        get { return _connectionString; }
    }

    public static TableAbcMapper TableAbc
    {
        get
        {
            if (_tableAbcMapper == null)
            {
                lock (typeof(TableAbcMapper))
                {
                    if (_tableAbcMapper == null)
                    {
                        _tableAbcMapper = new TableAbcMapper(_connectionString);
                    }
                }
            }
            return _tableAbcMapper;
        }
    }

    public static TableXyzMapper TableXyz
    {
        get
        {
            if (_tableXyzMapper == null)
            {
                lock (typeof(TableXyzMapper))
                {
                    if (_tableXyzMapper == null)
                    {
                        _tableXyzMapper = new TableXyzMapper(_connectionString);
                    }
                }
            }
            return _tableXyzMapper;
        }
    }

    // and about 30 more of these readonly properties
}

Every time I add or remove a table to the database I get to add a private volatile static field and that big ugly singleton-y property to the MyRepository class. My first idea was to make the properties call a generic instancing function within the class; something that looks like:

private static void InitMapper<TMapper>(TMapper instance) where TMapper : MyMapper
{
    lock (typeof(TMapper))
    {
        if (instance == null)
        {
            instance = Activator.CreateInstance(typeof(TMapper), 
                new object[] { _connectionString }) as TMapper;
        }
    }
}

Then the public getters could be slightly reduced to:

public static TableXyzMapper TableXyz
{
    get
    {
        if (_tableXyzMapper == null)
        {
            InitMapper<TableXyzMapper>(_tableXyzMapper);
        }
        return _tableXyzMapper;
    }
}

But, I don't know if passing around volatile fields is such a great idea, and using ref or out with volatile fields is a no-no, and on top of all that it doesn't reduce the amount of code all that much.

What I'd like to do is completely refactor the MyRepository class so that it has no private fields and no public getters, and uses reflection to initialize all of the mappers right away instead of lazy-loading them. I wouldn't have to change any of the code that uses the MyRepository class as it would look exactly the same, but under-the-hood it would be a little different:

public sealed class MyRepository
{
    private MyRepository()
    {
    }

    public volatile static TableAbcMapper TableAbc = null;
    public volatile static TableXyzMapper TableXyz = null;

    public static void Init(string connectionString)
    {
        foreach (var fieldInfo in typeof(MyRepository).GetFields(BindingFlags.Static))
        {
            if (fieldInfo.GetValue(new MyRepository()) == null)
            {
                lock (fieldInfo.FieldType)
                {
                    if (fieldInfo.GetValue(new MyRepository()) == null)
                    {
                        fieldInfo.SetValue(new MyRepository(), 
                            fieldInfo.FieldType.GetConstructor(new Type[] { typeof(string) })
                                .Invoke(new object[] { connectionString }));
                    }
                }
            }
        }
    }
}

Now the only maintenance I have to do when new tables are added to the database is to add a new public volatile static field for it, and the reflection would take care of the rest.

A few questions I have with this approach:

  • Is this approach functionally equivalent to the original class?
  • Is there any danger in defining volatile variables using reflection?
  • Is it as readable as the original class (assuming it's all commented)?

Lastly, if this is a question better suited for the Code Review site, I'm all for having it migrated (mods!).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

鱼窥荷 2024-12-19 04:47:03

它可能不会短得多,但由于您已经有一个 init 方法,您可以创建一个在第一次访问时创建的惰性值。 Lazy(.NET 4 的一部分)的好处是,您可以指定可以多次创建该值,但其值仅发布一次(提供更好的性能)。

class Program
    {
        static Lazy<string> _Lazy;
        static string _connectionString;

        public string LazyValue
        {
            get
            {
                return _Lazy.Value;
            }

        }

        public static void Init(string connectionString)
        {
            _connectionString = connectionString;
            _Lazy = new Lazy<string>(() => new string(connectionString.ToArray()), System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);
        }

但它不会变得更短。

It may be not much shorter but since you already have an init method you can there create a lazy value which is created upon first access. The nice thing about Lazy (part of .NET 4) is that you can specify that the value can be created more than one time but its value is only published once (gives better perf).

class Program
    {
        static Lazy<string> _Lazy;
        static string _connectionString;

        public string LazyValue
        {
            get
            {
                return _Lazy.Value;
            }

        }

        public static void Init(string connectionString)
        {
            _connectionString = connectionString;
            _Lazy = new Lazy<string>(() => new string(connectionString.ToArray()), System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);
        }

It won´t get much shorter though.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文