欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

程序员文章站 2022-04-10 14:06:11
前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要学习记录以下内容: 建议13、为类型输出格式化字符串 建议14、正确实现浅拷贝和深拷贝 建议15、使用dynamic来简化反射实现 建议13、为类型输出格式化字符串 有两种方法可以为 ......

前言

  本文已更新至 。本文主要学习记录以下内容:

  建议13、为类型输出格式化字符串

  建议14、正确实现浅拷贝和深拷贝

  建议15、使用dynamic来简化反射实现

建议13、为类型输出格式化字符串

   有两种方法可以为类型提供格式化的字符串输出。

  一种是意识到类型会产生格式化字符串输出,于是让类型继承接口iformattable。这对类型来说,是一种主动实现的方式,要求开发者可以预见类型在格式化方面的要求。

  更多的时候,类型的使用者需为类型自定义格式化器,这就是第二种方法,也是最灵活多变的方法,可以根据需求的变化为类型提供多个格式化器。

  下面我们就来看一下这两种方式的实现。

  最简单的字符串输出是为类型重写tostring()方法,如果没有为类型重写该方法,默认会调用ojbect的tostring方法,它会返回当前类型的类型名称。但即使是重写了tostring()方法,提供的字符串输出也是非常单一的,而通过实现iformattable接口的tostring()方法,可以让类型根据用户的输入而格式化输出。

下面我们来看一个简单的小例子:

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
    public class person:iformattable
    {
        public string idcode { get; set; }
        public string firstname { get; set; }
        public string lastname { get; set; }
        /// <summary>
        /// 实现接口iformattable的方法tostring
        /// </summary>
        /// <param name="format"></param>
        /// <param name="formatprovider"></param>
        /// <returns></returns>
        public string tostring(string format, iformatprovider formatprovider)
        {
            switch (format)
            { 
                case"ch":
                    return this.tostring();
                case"eg":
                    return string.format("{0}{1}", this.firstname, this.lastname);
                default:
                    return
                        this.tostring();
            }
        }
///重写object的方法tostring() public override string tostring() { return string.format("{0}{1}",this.lastname,this.firstname); } }
C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

调用代码如下所示:

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
        static void main(string[] args)
        {
            person person = new person() { firstname="kris",lastname="aehyok"};
            console.writeline(person);
            console.writeline(person.tostring("ch",null));
            console.writeline(person.tostring("eg", null));
            console.readline();
        }
C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

调用执行结果如下:

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

  下面我们来继续介绍第二实现方式——格式化器。如果类型本身没有提供格式化的功能,那么格式化器就可以派上用场了。格式化器的好处就是可以根据需求的变化,随时增加或者修改它。

  接下来我们继续来看另外的一个小例子:

首先定义一个实体类person:

    public class person
    {
        public string idcode { get; set; }
        public string firstname { get; set; }
        public string lastname { get; set; }
    }

一个典型的格式化器应该继承iformatprovider和icustomerformatter,看代码:

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
    public class personfomatter:iformatprovider,icustomformatter
    {
        #region iformatprovider成员
        public object getformat(type formattype)
        {
            if (formattype == typeof(icustomformatter))
            {
                return this;
            }
            else
            {
                return null;
            }
        }
        #endregion

        #region icustomformatter成员
        public string format(string format, object arg, iformatprovider formatprovider)
        {
            person person = arg as person;
            if (person == null)
            {
                return string.empty;
            }
            switch (format)
            { 
                case"ch":
                    return string.format("{0} {1}",person.lastname,person.firstname);
                case"":
                    return string.format("{0} {1}",person.firstname,person.lastname);
                case"chm":
                    return string.format("{0} {1}:{2}", person.lastname, person.firstname, person.idcode);
                default:
                    return string.format("{0} {1}", person.lastname, person.firstname);
            }
        }
        #endregion
    }
C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

调用代码如下:

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
    class program
    {
        static void main(string[] args)
        {
            person person = new person() { firstname="kris", lastname="aehyok", idcode="id000001"};
            console.writeline(person.tostring());
            personfomatter pfomatter = new personfomatter();
            console.writeline(pfomatter.format("ch", person, null));
            console.writeline(pfomatter.format("eg", person, null));
            console.writeline(pfomatter.format("chm", person, null));
            console.readline();
        }
    }
C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

调用执行结果如下:

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

其实还有另外一种变通的形式,就是将这两种方式合并一起使用的过程,下面来看一下具体的实现代码:

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
public class person:iformattable
    {
        public string idcode { get; set; }
        public string firstname { get; set; }
        public string lastname { get; set; }
                /// <summary>
        /// 实现接口iformattable的方法tostring
        /// </summary>
        /// <param name="format"></param>
        /// <param name="formatprovider"></param>
        /// <returns></returns>
        public string tostring(string format, iformatprovider formatprovider)
        {
            switch (format)
            { 
                case"ch":
                    return this.tostring();
                case"eg":
                    return string.format("{0}{1}", this.firstname, this.lastname);
                default:
                    //return this.tostring();
                    icustomformatter customerformatter = formatprovider as icustomformatter;
                    if (formatprovider == null)
                    {
                        return this.tostring();
                    }
                    return customerformatter.format(format, this, null);
            }
        }
        ///重写object的方法tostring()
        public override string tostring()
        {
            return string.format("{0}{1}",this.lastname,this.firstname);
        }
    }
C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

personfomatter自定义格式化器的代码并没有发生任何的改变。
调用代码如下:

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
        static void main(string[] args)
        {
            person person = new person() { firstname="kris", lastname="aehyok", idcode="id000001"};
            console.writeline(person.tostring());
            personfomatter pfomatter = new personfomatter();
            console.writeline(pfomatter.format("ch", person, null));
            console.writeline(pfomatter.format("eg", person, null));
            console.writeline(pfomatter.format("chm", person, null));

            console.writeline(person.tostring("ch",pfomatter));
            console.writeline(person.tostring("eg", pfomatter));
            console.writeline(person.tostring("chm", pfomatter));
            console.readline();
        }
C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

调用执行结果如下所示:

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

建议14、正确实现浅拷贝和深拷贝

为对象创建副本的技术成为拷贝(也叫克隆)。我们将拷贝分为浅拷贝和深拷贝。

浅拷贝 将对象中的所有字段复制到新的对象(副本)中。其中,值类型字段的值被复制到副本中后,在副本中的修改不会影响到源对象对应的值。 而引用类型的字段被复制到副本中的是引用类型的引用,而不是引用的对象,在副本中对引用类型的字段值做修改会影响到源对象本身。

深拷贝 同样,将对象中的所有字段复制到新的对象中。不过无论是对象的值类型字段,还是引用类型字段,都会被重新创建并赋值,对于副本的修改,不会影响到源对象本身。

无论是浅拷贝还是深拷贝,微软都建议用类型继承icloneable接口的方式明确告诉调用者:该类型可以被拷贝。当然,icloneable接口只提供了一个声明为clone的方法,我们可根据需求在clone方法内实现浅拷贝或深拷贝。一个简答的浅拷贝的实现代码如下所示:

首先定义实体类:

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
    public class employee:icloneable
    {
        public string idcode { get; set; }
        public int age { get; set; }
        public department department { get; set; }

        #region ocloneable成员
        public object clone()
        {
            return this.memberwiseclone();
        }
        #endregion
    }

    public class department
    {
        public string name{get;set;}
        public override string  tostring()
        {
              return this.name;
        }
    }
C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

然后进行调用代码如下:

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
        static void main(string[] args)
        {
            employee niki = new employee()
            {
                idcode = "idaehyok",
                age = 25,
                department = new department() { name="depart1" }
            };
            employee kris = niki.clone() as employee;
            console.writeline(string.format("idcode:{0}\tage:{1}\tdepartment:{2}", kris.idcode, kris.age, kris.department));
            ///开始改变niki的值
            niki.idcode = "idniki";
            niki.age = 23;
            niki.department.name = "depart2";
            console.writeline(string.format("idcode:{0}\tage:{1}\tdepartment:{2}", kris.idcode, kris.age, kris.department));
            console.readline();
        }
C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

调用执行结果如下

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

注意到employee的idcode属string类型。理论上string类型是引用类型,但是由于该引用类型的特殊性(无论是实际还是语义),object.memberwiseclone方法仍旧为其创建了副本。也就是说,在浅拷贝过程,我们应该将字符串看成是值类型。employee的department属性是一个引用类型,所以,如果改变了源对象niki中的值,那么副本kris中的值也会随之一起变动。

 employee的深拷贝有多种实现方法,最简单的方式是手动的对字段进行逐个的赋值。但是这种方法容易出错,也就是说,如果类型的字段发生变化或有增减,那么该拷贝方法也要发生相应的变化,所以,建议使用序列化的形式来进行深拷贝。employee深拷贝的一种实现方式如下:

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
    [serializable]
    public class employee:icloneable
    {
        public string idcode { get; set; }
        public int age { get; set; }
        public department department { get; set; }

        #region ocloneable成员
        public object clone()
        {
            using (stream objectstream = new memorystream())
            {
                iformatter formatter = new binaryformatter();
                formatter.serialize(objectstream, this);
                objectstream.seek(0, seekorigin.begin);
                return formatter.deserialize(objectstream) as employee;
            }
        }
        #endregion
    }

    [serializable]
    public class department
    {
        public string name{get;set;}
        public override string tostring()
        {
              return this.name;
        }
    }
C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

调用方法如下所示:

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
            employee niki = new employee()
            {
                idcode = "idaehyok",
                age = 25,
                department = new department() { name="depart1" }
            };
            employee kris = niki.clone() as employee;
            console.writeline(string.format("idcode:{0}\tage:{1}\tdepartment:{2}", kris.idcode, kris.age, kris.department));
            ///开始改变niki的值
            niki.idcode = "idniki";
            niki.age = 23;
            niki.department.name = "depart2";
            console.writeline(string.format("idcode:{0}\tage:{1}\tdepartment:{2}", kris.idcode, kris.age, kris.department));
            console.readline();
C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

最终代码调用结果如下

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

可以发现再次改变niki的值,不会对副本kris产生影响。

由于接口icloneable,只有一个模棱两可的方法,所以,如果要在一个类中进行浅拷贝和深拷贝,只能由我们额外的实现两个方法。声明为deepclone和shallow。那么最终代码如下所示:

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
    [serializable]
    public class employee:icloneable
    {
        public string idcode { get; set; }
        public int age { get; set; }
        public department department { get; set; }

        #region ocloneable成员
        public object clone()
        {
            return this.memberwiseclone();
        }
        #endregion

        public employee deeptclone()
        {
            using (stream objectstream = new memorystream())
            {
                iformatter formatter = new binaryformatter();
                formatter.serialize(objectstream, this);
                objectstream.seek(0, seekorigin.begin);
                return formatter.deserialize(objectstream) as employee;
            }
        }

        public employee shallow()
        {
            return clone() as employee;
        }
    }
C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

 

建议15、使用dynamic来简化反射实现

  dynamic是framework4.0的新特性。dynamic的出现让c#具有了弱类型的特性。编译器在编译的时候不再对类型进行检查,编译器默认dynamic对象支持开发者想要的任何类型。如果运行时不包含指定的特性,运行时程序会抛出一个runtimebinderexception异常。

下面我们先来看一个简单的例子

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
    public class dynamicsample
    {
        public string name { get; set; }
        public int add(int a, int b)
        {
            return a + b;
        }
    }
C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

现在我们想调用上面实体类的add方法,实现方式可以是这样的:

            dynamicsample dynamicsample = new dynamicsample();
            var addmethod = typeof(dynamicsample).getmethod("add");
            int re = (int)addmethod.invoke(dynamicsample, new object[] { 1, 2 });
            console.writeline(re);

下面我们再通过使用dynamic来实现一下:

            dynamic dynamic = new dynamicsample();
            int re2 = dynamic.add(1, 2);
            console.writeline(re2);

可以发现dynamic的实现方式很简洁,而且性能也有所提升,当然上面一次的调用我们是看不出什么效果的,假如上面的代码我们进行调用了10000000次。

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]
            int times = 10000000;
            ////第一种调用方式
            dynamicsample reflectsample = new dynamicsample();
            var addmethod = typeof(dynamicsample).getmethod("add");
            stopwatch watch1 = stopwatch.startnew();///用于测试运行时间
            for (var i = 0; i < times; i++)
            {
                addmethod.invoke(reflectsample, new object[] { 1, 2 });
            }
            console.writeline(string.format("普通方法反射耗时:{0} 毫秒", watch1.elapsedmilliseconds));

            ////第二种调用方式
            dynamic dynamicsample = new dynamicsample();
            stopwatch watch2 = stopwatch.startnew();
            for (int i = 0; i < times; i++)
            {
                dynamicsample.add(1, 2);
            }
            console.writeline(string.format("dynamic方式耗时:{0} 毫秒", watch2.elapsedmilliseconds));

            ////第三种调用方式
            dynamicsample reflectsamplebetter = new dynamicsample();
            var addmethod2 = typeof(dynamicsample).getmethod("add");
            var delg = (func<dynamicsample, int, int, int>)delegate.createdelegate(typeof(func<dynamicsample, int, int, int>), addmethod2);
            stopwatch watch3 = stopwatch.startnew();
            for (var i = 0; i < times; i++)
            {
                delg(reflectsamplebetter, 1, 2);
            }
            console.writeline(string.format("优化的反射耗时:{0} 毫秒", watch3.elapsedmilliseconds));
            console.readline();
C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

调用执行后的结果为

C#程序编写高质量代码改善的157个建议【13-15】[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

现在可以看出很明显的区别,普通方法调用发射执行效率远远的低于使用dynamic。第三种方式是我们优化了发射之后的执行时间,比使用dynamic也有所提升,但是并不是特别明显,虽然带来了性能的提升,不过却牺牲了代码的整洁性。这种实现方式在我看来是得不偿失的。所以建议大家使用dynamic来优化发射。