手动实现 C# 版简单版控制反转

940 阅读1分钟

一,原理

使用反射加 xml 或者特性解析实现

二,实现

1,xml 实现

  • class 代码
// User 类
namespace CsharpIOC
{
    public class User
    {
        public string name { get; set; }
        public string age { get; set; }
    }
}
  • Xml容器类
public class XmlApplicationContext
    {
        /// <summary>
        /// bean 容器
        /// </summary>
        private static Dictionary<string, object> _beanContainer = new Dictionary<string, object>();

        public XmlApplicationContext(string xmlPath)
        {
            XmlDocument xmlDoc = new XmlDocument();
            //读取 xml 文件
            xmlDoc.Load(xmlPath);
            //获取 xml 文件中所有的 bean 节点
            XmlNodeList nodeList = xmlDoc.SelectNodes("Bean");
            foreach (XmlNode node in nodeList)
            {
                //bean id
                string beanId = string.Empty;
                //bean 的全类名,命名空间+类名
                string beanClass = string.Empty;
                foreach (XmlNode child in node.Attributes)
                {
                    if (child.Name.Equals("id"))
                    {
                        beanId = child.Value;
                    }
                    else if (child.Name.Equals("class"))
                    {
                        beanClass = child.Value;
                    }
                }
                //通过反射创建类的实例
                Type beanType = Type.GetType(beanClass);
                object bean = Activator.CreateInstance(beanType);
                //接下来调用set方法注入属性
                foreach (XmlNode child in node.ChildNodes)
                {
                    //获取该类的所有方法
                    var methods = beanType.GetMethods();
                    foreach (var method in methods) {
                        //默认的 set 方法名是 set_属性名,例如 set_name
                        if (method.Name.Equals($"set_{child.Name}")) {
                            //调用 set 方法注入属性
                            method.Invoke(bean,new object[] { child.InnerText});
                        }
                    }
                }
                //添加 bean 到容器中
                _beanContainer[beanId] = bean;
            }
        }

        /// <summary>
        /// 通过 bean id 获取 bean 实例
        /// </summary>
        /// <param name="beanId"></param>
        /// <returns></returns>
        public object getBean(string beanId) => _beanContainer[beanId];
    }
  • xml 配置
<?xml version="1.0" encoding="utf-8" ?>
<Bean id ="user" class="CsharpIOC.User">
  <name>任我行</name>
  <age>45</age>
</Bean>
  • 测试
XmlApplicationContext ctx = new XmlApplicationContext("Bean.xml");
User user = (User)ctx.getBean("user");
Console.WriteLine(user.name);
Console.WriteLine(user.age);

2,特性实现

  • 特性准备
/// <summary>
    /// 标记类可被容器扫描
    /// </summary>
    public class Component : Attribute
    {
        public string id { get; set; }
    }

    /// <summary>
    /// 标记字段的注入值
    /// </summary>
    public class Value : Attribute
    {
        public object value { get; set; }
    }
  • class
namespace CsharpIOC
{
    [Component(id = "user")]
    public class User
    {
        [Value(value = "东方不败")]
        public string name { get; set; }
        [Value(value = "99")]
        public string age { get; set; }
    }
}
  • 特性容器类
public class AttributeaApplicationContext
    {
        /// <summary>
        /// bean 容器
        /// </summary>
        private static Dictionary<string, object> _beanContainer = new Dictionary<string, object>();

        public AttributeaApplicationContext(string nameSpace)
        {
            //加载命名空间
            Assembly asm = Assembly.Load(nameSpace);
            //遍历命名空间下的类
            foreach (Type type in asm.DefinedTypes)
            {
                //找出带有 Componment 自定义特性标记的类
                var attribute = type.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Equals(typeof(Component)));
                if (attribute != null)
                {
                    //读取 Componment 中的 bean id
                    string beanId = attribute.NamedArguments.FirstOrDefault(na => na.MemberName.Equals("id")).TypedValue.Value.ToString();
                    //实例化该 bean 对象
                    object bean = Activator.CreateInstance(type);
                    var properties = type.GetProperties();
                    //遍历该 bean 的属性
                    foreach (var property in properties)
                    {
                        //找出带有 Value 自定义特性标记的 bean 属性
                        var proAttribute = property.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Equals(typeof(Value)));
                        if (proAttribute != null)
                        {
                            //获取 Value 特性的值并注入
                            var kv = proAttribute.NamedArguments.FirstOrDefault(na => na.MemberName.Equals("value"));
                            var method = type.GetMethods().FirstOrDefault(m => m.Name.Equals($"set_{property.Name}"));
                            if (method != null && kv != null) {
                                method.Invoke(bean,new object[] { kv.TypedValue.Value});
                            }
                        }
                    }
                    //添加 bean 到容器中
                    _beanContainer[beanId] = bean;
                }
            }
        }

        /// <summary>
        /// 通过 bean id 获取 bean 实例
        /// </summary>
        /// <param name="beanId"></param>
        /// <returns></returns>
        public object getBean(string beanId) => _beanContainer[beanId];
    }
  • 测试
AttributeaApplicationContext ctx = new AttributeaApplicationContext("CsharpIOC");
User user = (User)ctx.getBean("user");
Console.WriteLine(user.name);
Console.WriteLine(user.age);

三,结语

其实大部分已知的框架都是使用反射和设计模式组合实现的,IOC的核心便是反射,本文只是大概讲述其原理,实际其中还有很多的细节是没有讲到的,比如调用set方法时的参数类型转换,复杂的内嵌属性注入等等。