效率:Func <T>,Vs的实例

Ric*_*ard 4 .net c# generics functional-programming

最近我一直在尝试使用这门Func<T>课程,到目前为止我很喜欢它.然而我注意到越来越多的我开始使用它而不是实际使用的实例T,所以我想问; 使用Func<T>vs 的开销是T多少?我知道这是一个有点普遍的问题,T可以是任何东西,所以我想这个问题应该集中在,传递函数的开销是什么,而不是简单对象的实例?

为了论证,我们假设如下.

我们的模拟对象, T

public class Person
{
    private string _name = string.Empty;
    private int _age = 0;
    private bool _isMale = true;

    public Person(string name, int age, bool isMale)
    {
        this.Name = name;
        this.Age = age;
        this.IsMale = isMale;
    }

    public string Name
    {
        get { return this._name; }
        set { this._name = value; }
    }

    public int Age
    {
        get { return this._age; }
        set { this._age = value; }
    }

    public bool IsMale
    {
        get { return this._isMale; }
        set { this._isMale = value; }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,假设我们有一个非常好的扩展方法IDictionary,它通过键默认值选择值.伪代码可以描述如下:
KeyValuePair集合中找到的密钥是,返回值No,返回默认值

选项1.我们的扩展方法使用的实例 T

public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary source, TKey key, TValue @default)
{
    if (source.ContainsKey(key))
    {
        return source[key];
    }
    return @default;
}

// usage
var myValue = myDictionary.GetValueOrDefault("Richard", new Person());
Run Code Online (Sandbox Code Playgroud)

选项2.我们的扩展方法使用Func<T>...... mmm,漂亮!

public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary source, TKey key, Func<TValue> defaultSelector)
{
    if (source.ContainsKey(key))
    {
        return source[key];
    }
    return defaultSelector();
}

// usage
var myValue = myDictionary.GetValueOrDefault("Richard", () => new Person("Richard", 25, true));
Run Code Online (Sandbox Code Playgroud)

对照

比较上述选项,很明显两者都有潜在的好处.选项1稍微容易阅读,但我现在喜欢使用Func<T>,因此对我来说选项2似乎是理想的.我想我认为它是一个懒惰实例化的参数,只在需要时执行,因此节省了效率,但我是对的吗?

Pen*_*hev 6

这是我用于基准测试的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace ConsoleApplication3
{
    using System.Collections;
    using System.Diagnostics;
    using System.Globalization;
    using System.Numerics;
    using System.Xml.Linq;

    public class Program
    {

        public class Person
        {
            private string _name = string.Empty;

            private int _age = 0;

            private bool _isMale = true;

            public Person(string name, int age, bool isMale)
            {
                this.Name = name;
                this.Age = age;
                this.IsMale = isMale;
            }

            public string Name
            {
                get
                {
                    return this._name;
                }
                set
                {
                    this._name = value;
                }
            }

            public int Age
            {
                get
                {
                    return this._age;
                }
                set
                {
                    this._age = value;
                }
            }

            public bool IsMale
            {
                get
                {
                    return this._isMale;
                }
                set
                {
                    this._isMale = value;
                }
            }
        }

        private static void Main(string[] args)
        {
            var myDictionary = new Dictionary<string, Person>();
            myDictionary.Add("notRichard", new Program.Person("Richard1", 26, true));
            myDictionary.Add("notRichard1", new Program.Person("Richard2", 27, true));
            myDictionary.Add("notRichard2", new Program.Person("Richard3", 28, true));
            myDictionary.Add("notRichard3", new Program.Person("Richard4", 29, true));
            // usage
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for(int i = 0; i < 100000000; i++)
            {
                var myValue = myDictionary.GetValueOrDefault("Richard", new Program.Person("Richard", 25, true));
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 100000000; i++)
            {
                var myValue = myDictionary.GetValueOrDefault("Richard", ()=> new Program.Person("Richard", 25, true));
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            Console.ReadKey();
        }
    }
    public static class Ex
    {
        public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> source, TKey key, TValue @default)
        {
            if (source.ContainsKey(key))
            {
                return source[key];
            }
            return @default;
        }
        public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> source, TKey key, Func<TValue> defaultSelector)
        {
            if (source.ContainsKey(key))
            {
                return source[key];
            }
            return defaultSelector();
        }


    }
}
Run Code Online (Sandbox Code Playgroud)

调用每个extenssion方法100000000次(没有找到条目,因此每次都执行Func)得到以下结果:

T - 10352毫秒

Func<T> - 12268毫秒

调用每个extenssion方法100000000次(并找到一个条目,因此根本不调用Func)给出以下结果:

T - 15578毫秒

Func<T> - 11072毫秒

因此,哪一个执行得更快取决于您节省了多少实例化以及每个实例化的成本.

通过重用默认人员实例来优化代码,为6809 ms T和7452提供Func<T>:

            Stopwatch sw = new Stopwatch();
            var defaultPerson = new Program.Person("Richard", 25, true);
            sw.Start();
            for(int i = 0; i < 100000000; i++)
            {
                var myValue = myDictionary.GetValueOrDefault("Richard", defaultPerson);
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 100000000; i++)
            {
                var myValue = myDictionary.GetValueOrDefault("Richard", () => defaultPerson);
            }
Run Code Online (Sandbox Code Playgroud)

因此,理论上(如果您将实例化从等式中取出),在调用堆栈中保存跳跃可以获得一些性能提升,但实际上,这种增益可以忽略不计.