簡體   English   中英

.NET 4.0中的動態:我做得對嗎?

[英]Dynamics in .NET 4.0: am I doing it right?

昨天我使用.NET 4.0中的新dynamic類型編寫了我的第一行代碼。 我發現這個有用的場景如下:

我有一個類,里面有幾個值列表。 這可以是List<string>List<bool>List<int>或任何類型的列表。 使用它們的方式是我向這些列表中的一個或多個添加值。 然后我“同步”它們,以便它們都以相同的長度結束(那些太短的都填充了默認值)。 然后我繼續添加更多值,再次同步等。目標是其中一個列表中任何索引處的項與另一個列表中相同索引處的項相關。 (是的,通過將所有這些包裝在另一個類中可能可以更好地解決這個問題,但在這種情況下,這不是重點。)

我有幾個類中的這個構造,所以我想讓列表的同步盡可能通用。 但由於列表的內部類型可能會有所不同,這並不像我最初想象的那樣直截了當。 但是,進入當天的英雄:動態:)

我編寫了以下幫助器類,它可以獲取列表(任何類型)的集合以及每個列表的默認值:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Foo.utils
{
    public class ListCollectionHelper
    {
        /// <summary>
        /// Takes a collection of lists and synchronizes them so that all of the lists are the same length (matching
        /// the length of the longest list present in the parameter).
        /// 
        /// It is assumed that the dynamic type in the enumerable is of the type Tuple&lt;ICollection&lt;T>, T>, i.e. a
        /// list of tuples where Item1 is the list itself, and Item2 is the default value (to fill the list with). In
        /// each tuple, the type T must be the same for the list and the default value, but between the tuples the type
        /// might vary.
        /// </summary>
        /// <param name="listCollection">A collection of tuples with a List&lt;T> and a default value T</param>
        /// <returns>The length of the lists after the sync (length of the longest list before the sync)</returns>
        public static int SyncListLength(IEnumerable<dynamic> listCollection)
        {
            int maxNumberOfItems = LengthOfLongestList(listCollection);
            PadListsWithDefaultValue(listCollection, maxNumberOfItems);
            return maxNumberOfItems;
        }

        private static int LengthOfLongestList(IEnumerable<dynamic> listCollection)
        {
            return listCollection.Aggregate(0, (current, tuple) => Math.Max(current, tuple.Item1.Count));
        }

        private static void PadListsWithDefaultValue(IEnumerable<dynamic> listCollection, int maxNumberOfItems)
        {
            foreach (dynamic tuple in listCollection)
            {
                FillList(tuple.Item1, tuple.Item2, maxNumberOfItems);
            }
        }

        private static void FillList<T>(ICollection<T> list, T fillValue, int maxNumberOfItems)
        {
            int itemsToAdd = maxNumberOfItems - list.Count;

            for (int i = 0; i < itemsToAdd; i++)
            {
                list.Add(fillValue);
            }
        }
    }
}

以下是我用來驗證我最終得到所需行為的一小部分單元測試:

using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Foo.utils;

namespace Foo.UnitTests
{
    [TestClass]
    public class DynamicListSync
    {
        private readonly List<string> stringList = new List<string>();
        private readonly List<bool> boolList = new List<bool>();
        private readonly List<string> stringListWithCustomDefault = new List<string>();
        private readonly List<int> intList = new List<int>();

        private readonly List<dynamic> listCollection = new List<dynamic>();

        private const string FOO = "bar";

        [TestInitialize]
        public void InitTest()
        {
            listCollection.Add(Tuple.Create(stringList, default(String)));
            listCollection.Add(Tuple.Create(boolList, default(Boolean)));
            listCollection.Add(Tuple.Create(stringListWithCustomDefault, FOO));
            listCollection.Add(Tuple.Create(intList, default(int)));
        }

        [TestMethod]
        public void SyncEmptyLists()
        {
            Assert.AreEqual(0, ListCollectionHelper.SyncListLength(listCollection));
        }

        [TestMethod]
        public void SyncWithOneListHavingOneItem()
        {
            stringList.Add("one");
            Assert.AreEqual(1, ListCollectionHelper.SyncListLength(listCollection));

            Assert.AreEqual("one", stringList[0]);
            Assert.AreEqual(default(Boolean), boolList[0]);
            Assert.AreEqual(FOO, stringListWithCustomDefault[0]);
            Assert.AreEqual(default(int), intList[0]);
        }

        [TestMethod]
        public void SyncWithAllListsHavingSomeItems()
        {
            stringList.Add("one");
            stringList.Add("two");
            stringList.Add("three");
            boolList.Add(false);
            boolList.Add(true);
            stringListWithCustomDefault.Add("one");

            Assert.AreEqual(3, ListCollectionHelper.SyncListLength(listCollection));

            Assert.AreEqual("one", stringList[0]);
            Assert.AreEqual("two", stringList[1]);
            Assert.AreEqual("three", stringList[2]);

            Assert.AreEqual(false, boolList[0]);
            Assert.AreEqual(true, boolList[1]);
            Assert.AreEqual(default(Boolean), boolList[2]);

            Assert.AreEqual("one", stringListWithCustomDefault[0]);
            Assert.AreEqual(FOO, stringListWithCustomDefault[1]);
            Assert.AreEqual(FOO, stringListWithCustomDefault[2]);

            Assert.AreEqual(default(int), intList[0]);
            Assert.AreEqual(default(int), intList[1]);
            Assert.AreEqual(default(int), intList[2]);
        }
    }
}

所以,既然這是我動態的第一次鏡頭(無論是在C#還是其他任何地方......),我只想問我是否正確行事。 顯然代碼按預期工作,但這是正確的方法嗎? 我有什么明顯的優化或缺陷嗎?

我已經對C#中的動態做了很多討論,我最初認為它們會非常整潔,因為我是Ruby / Javascript完成的動態類型的忠實粉絲,但在實現中卻很遺憾。 因此,我對“我是否正確行事”的看法歸結為“這個問題非常適合動態” - 這是我對此的看法。

  • 拉入的負載性能和JIT所有與動態相關的組件可能非常嚴重。
  • C-Sharp運行時綁定程序在內部拋出並在第一次動態解析方法時捕獲異常。 每個調用站點都會發生這種情況(例如,如果您在動態對象上有10行代碼調用方法,則會獲得10個異常)。 如果您將調試器設置為“在第一次機會異常時中斷”,這實際上很煩人,並且它還會使用第一次機會異常消息填滿調試輸出窗口。 你可以抑制這些,但是visual studio讓你很煩人。
  • 這兩件事情加起來 - 冷啟動你的應用程序可能需要更長時間才能加載。 在帶有SSD的核心i7上,我發現當我的WPF應用程序首次加載所有動態內容時,它會停止大約1-2秒加載程序集,JITing和拋出異常。 (有趣的是IronRuby沒有這些問題,它的DLR實現比C#好得多)
  • 加載東西后,性能非常好。
  • 動態殺死智能感知和視覺工作室的其他不錯的功能。 雖然我個人並不介意,因為我有很多ruby代碼的背景,我組織中的其他幾個開發人員都很生氣。
  • 動力學可以使調試變得更難。 像ruby / javascript這樣的語言提供了一個幫助他們的REPL(交互式提示),但C#還沒有。 如果您只是使用動態來解析方法,它就不會那么糟糕,但如果您嘗試使用它來動態實現數據結構(ExpandoObject等),那么調試將成為C#的真正痛苦。 當他們不得不使用ExpandoObject調試一些代碼時,我的同事對我更加惱火。

總體上:

  • 如果您可以在不需要動態的情況下做某事,請不要使用它們。 C#實現它們的方式太尷尬了,你的同事會對你生氣。
  • 如果您只需要一個非常小的動態特征,請使用反射。 使用反射的經常引用的“性能問題”通常不是什么大問題。
  • 特別是由於負載/啟動性能損失,請嘗試避免客戶端應用程序中的動態。

我對這種具體情況的建議:

  • 看起來你可以通過傳遞物體作為Object來避免動態。 我建議你這樣做。
  • 您將不得不切換使用Tuple來傳遞您的數據對,並制作一些自定義類,但這可能也會改進您的代碼,因為您可以將有意義的名稱附加到數據而不僅僅是Item1Item2

我相信動態關鍵字主要是為了使Microsoft Office interop更容易添加,以前你必須編寫非常復雜的代碼(在C#中)才能使用Microsoft Office API,Office界面代碼現在可以更加清晰。

對此的共鳴是Office API最初編寫為由Visual Basic 6(或VB腳本)使用; .NET 4.0添加了幾種語言功能,以使其更容易(以及動態,也可以獲得命名和可選參數)。

當您使用dynamic關鍵字時,它會丟失編譯時檢查,因為使用dynamic關鍵字的對象在運行時被解析。 由於必須加載提供動態支持的程序集,因此存在一些內存開銷。此外,還會有一些性能開銷,類似於使用Reflection。

我不認為這是動態的解決方案。 當您需要有條件地處理一堆不同類型時,動態非常有用。 如果這是一個字符串,做一些事情,如果它是一個int,做其他事情,如果它是一個Puppy類實例,則調用bark()。 動態釋放你不必像這樣亂丟垃圾代碼與大量的類型鑄造或丑陋的泛型。 動態和其他高級語言功能的用途適用於代碼生成器,解釋器等......

這是一個很酷的功能,但除非你使用動態語言或COM互操作,否則它只適用於你有一個討厭的高級問題。

我認為你可以通過使用IList來完成同樣的事情,而不是在這里使用動態。 (非泛型)兩者都消除了編譯時類型檢查,但由於泛型列表也實現了IList ,您仍然可以使用IList運行時類型檢查。

另外,側面問題,為什么你使用.Aggregate()而不是.Max()來查找最大長度?

我還沒有密切關注它,但在聲明集合時,是否真的需要使用動態關鍵字? 在.NET 4.0中,還有支持協方差和逆變的新機制 ,這意味着您還應該能夠使用下面的代碼。

    var listCollection = new List<IEnumerable<object>>();

    listCollection.Add(new List<int>());

這里的缺點是你的列表只包含IEnumerable實例,而不是可以直接修改的東西,如果你的實現需要的話。

除了這個考慮之外,我認為動態的使用很好,因為你正在使用它們,但你確實犧牲了C#通常提供的許多安全機制。 所以我建議如果你使用這種技術我會建議在一個包含良好且經過測試的類中編寫它,它不會將動態類型暴露給任何更大的客戶端代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM