【摘錄】使用實體框架、Dapper和Chain的倉儲模式實現策略

来源:http://www.cnblogs.com/rjf1979/archive/2017/01/11/6272812.html
-Advertisement-
Play Games

以下文章摘錄來自InfoQ,是一篇不錯的軟問,大家細細的品味 關鍵要點: Dapper這類微ORM(Micro-ORM)雖然提供了最好的性能,但也需要去做最多的工作。 在無需複雜對象圖時,Chain這類Fluent ORM更易於使用。 對實體框架(Entity Framework)做大量的工作後,其 ...


以下文章摘錄來自InfoQ,是一篇不錯的軟問,大家細細的品味

關鍵要點:

  • Dapper這類微ORM(Micro-ORM)雖然提供了最好的性能,但也需要去做最多的工作。
  • 在無需複雜對象圖時,Chain這類Fluent ORM更易於使用。
  • 對實體框架(Entity Framework)做大量的工作後,其性能可顯著提高。
  • 為獲得資料庫的最大性能,需要採用可能會有些繁瑣的投影(Projection)操作。
  • ORM整體上的局部更新可能會存在問題。

在現代企業開發中,可採用多種方法構建數據存取層(data access layer ,DAL)。使用C#做開發時,DAL的最底層幾乎總是使用ADO.NET。但這時常會形成一個笨重的庫,所以通常會在DAL的底層之上再部署一個ORM層。為允許模擬和隱藏ORM的細節,整個DAL包裝在存儲內。

在這一系列的文章中,我們將審視三種使用不同類型ORM構建倉儲模式的方法,分別是:

  • 實體框架:一種傳統的“全特性”或“OOP”類型的ORM。
  • Dapper:一種主要專註結果集映射的輕量級微ORM。
  • Tortuga Chain:一種基於函數式編程理念的Fluent ORM。
 

本文將側重於開發人員可在典型倉儲中用到的那些基本功能。在本系列文章的第二部分,我們將著眼於那些開發人員基於實際情況而實現的高級技術。

插入(Insert)操作

對於任何CRUD操作集,通常會首先實現基本的插入操作,進而可用插入操作對其它的操作進行測試。

Chain

Chain使用列名和屬性名間的運行時匹配。對於在資料庫中並不存在的對象,除非啟用了嚴格模式(strict model),否則將忽略該對象上的屬性。類似地,沒有匹配屬性的列不能成為生成SQL的組成部分。

相關廠商內容

     
public int Insert(Employee employee)
        {
            return m_DataSource.Insert("HR.Employee", employee).ToInt32().Execute();
        }

Dapper

沒有第三方擴展時,Dapper需要編程人員手工指定所需的SQL,其中包括了特定於資料庫的邏輯,用於返回新創建的主鍵。

 public int Insert(Employee employee)
        {
            const string sql = @"INSERT INTO HR.Employee
        (FirstName,
         MiddleName,
         LastName,
         Title,
         ManagerKey,
         OfficePhone,
         CellPhone
        )
VALUES  (@FirstName,
         @MiddleName,
         @LastName,
         @Title,
         @ManagerKey,
         @OfficePhone,
         @CellPhone
        );

SELECT SCOPE_IDENTITY()
";
            using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                return con.ExecuteScalar<int>(sql, employee);
            }
        }

實體框架

實體框架使用編譯階段映射在運行時生成SQL。需將任何沒有匹配列的屬性標記為NotMapped,否則將會產生錯誤。

public int Insert(Employee employee)
        {
            using (var context = new CodeFirstModels())
            {
                context.Employees.Add(employee);
                context.SaveChanges();
                return employee.EmployeeKey;
            }
        }

更新(Update)操作

Chain

Chain預設使用資料庫中所定義的主鍵。但是在設置了適當的插入選項後,它將在模型中使用Key屬性。

public void Update(Employee employee)
        {
            m_DataSource.Update("HR.Employee", employee).Execute();
        }

Dapper

與插入操作一樣,純Dapper需用戶手工編寫必要的SQL語句。

public void Update(Employee employee)
    {
            const string sql = @"UPDATE HR.Employee
    SET     FirstName = @FirstName,
            MiddleName = @MiddleName,
            LastName = @LastName,
            Title = @Title,
            ManagerKey = @ManagerKey,
            OfficePhone = @OfficePhone,
            CellPhone = @CellPhone
    WHERE   EmployeeKey = @EmployeeKey
    ";
            using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                con.Execute(sql, employee);
            }
     }

實體框架(初學者)

實體框架為UPDATE語句查找Key屬性,以生成WHERE語句。

public void Update(Employee employee)
        {
            using (var context = new CodeFirstModels())
            {
                var entity = context.Employees.Where(e => e.EmployeeKey == employee.EmployeeKey).First();
                entity.CellPhone = employee.CellPhone;
                entity.FirstName = employee.FirstName;
                entity.LastName = employee.LastName;
                entity.ManagerKey = employee.ManagerKey;
                entity.MiddleName = employee.MiddleName;
                entity.OfficePhone = employee.OfficePhone;
                entity.Title = employee.Title;
                context.SaveChanges();
            }
        }

實體框架(中級用戶)

使用實體框架時,初學者常會在執行更新操作上犯錯誤。將實體添加到上下文中很容易就能實現它,而這種模式應成為中級使用者的常識。這裡給出使用實體狀態“Modified”修正後的例子。

public void Update(Employee employee)
        {
            using (var context = new CodeFirstModels())
            {
                context.Entry(employee).State = EntityState.Modified;
                context.SaveChanges();
            }
        }

讀取全部(Read All)操作

讀取全部操作在實體框架和Chain中是十分相似的,不同之處在於在實體框架中實現需要編寫更多行的代碼,而在Chain中實現需要編寫更長的代碼行。

Dapper當然是最為繁瑣的,因為它需要未經加工的SQL語句。即使如此,仍可以通過使用SELECT *語句替代手工地指定列名而在一定程度上降低Dapper的開銷。這在存在返回額外數據的風險的情況下,降低了出現類與SQL語句不匹配的可能性。

Chain

在Chain中,ToObject連接生成一系列所需的列。通過匹配所需列表與可用列的列表,From連接生成SQL語句。

public IList<Employee> GetAll()
        {
            return m_DataSource.From("HR.Employee").ToCollection<Employee>().Execute();
        }

Dapper

Dapper是最為繁瑣的,因為它需要原始未經加工的SQL語句。雖然這令人皺眉頭,但仍可以通過使用SELECT *語句替代手工地指定列名而在一定程度上降低Dapper的開銷,這樣是不太可能漏掉列的,雖然存在返回額外數據的風險。

 public IList<Employee> GetAll()
        {
            using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                return con.Query<Employee>("SELECT e.EmployeeKey, e.FirstName, e.MiddleName, e.LastName, e.Title, e.ManagerKey, e.OfficePhone, e.CellPhone, e.CreatedDate FROM HR.Employee e").AsList();
            }
        }

實體框架

像以前一樣,實體框架使用編譯期信息確定如何生成SQL語句。

public IList<Employee> GetAll()
        {
            using (var context = new CodeFirstModels())
            {
                return context.Employees.ToList();
            }
        }

按標識符獲取(Get by Id)操作

需要註意的是,隨每個例子的語法稍作修改就可表明只返回一個對象。同樣的基本過濾技術可用於返回多個對象。

Chain

Chain嚴重依賴於“過濾對象”。這些對象直接被轉義成參數化的WHERE語句,語句中的每個屬性間具有“AND”操作符。

public Employee Get(int employeeKey)
        {
            return m_DataSource.From("HR.Employee", new { @EmployeeKey = employeeKey }).ToObject<Employee>().Execute();
        } 

Chain也允許用參數化的字元串表示WHERE語句,雖然這個功能很少被用到。

如果主鍵是標量,即主鍵中只有一列,那麼可使用簡化的語法。

public Employee Get(int employeeKey)
        {
            return m_DataSource.GetByKey("HR.Employee", employeeKey).ToObject<Employee>().Execute();
        }

Dapper

下例中,可以看到Dapper手工指定了SQL語句。該語句與Chain和實體框架所生成的SQL語句在本質上是一致的。

using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                return con.Query<Employee>("SELECT e.EmployeeKey, e.FirstName, e.MiddleName, e.LastName, e.Title, e.ManagerKey, e.OfficePhone, e.CellPhone, e.CreatedDate FROM HR.Employee e WHERE e.EmployeeKey = @EmployeeKey", new { @EmployeeKey = employeeKey }).First();
            }

實體框架

實體框架將表名和首個ToList或First操作間的所有內容看作為一個表達式樹。在運行時評估該樹以生成SQL語句。

public Employee Get(int employeeKey)
        {
            using (var context = new CodeFirstModels())
            {
                return context.Employees.Where(e => e.EmployeeKey == employeeKey).First();
            }
        }

刪除(Delete)操作

Chain

Chain期待包括主鍵的參數對象。而參數對象中的其它特性將被忽略(該語法不支持批量刪除)。

public void Delete(int employeeKey)
        {
            m_DataSource.Delete("HR.Employee", new { @EmployeeKey = employeeKey }).Execute();
        }

如果有標量主鍵,可使用簡化的語法。

 public void Delete(int employeeKey)
        {
            m_DataSource.DeleteByKey("HR.Employee", employeeKey).Execute();
        }

Dapper

public void Delete(int employeeKey)
        {
            using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                con.Execute("DELETE FROM HR.Employee WHERE EmployeeKey = @EmployeeKey", new { @EmployeeKey = employeeKey });
            }
        }

實體框架(初學者)

初學者一般會取回一個記錄然後迅速刪除,丟棄所有返回的信息。

public void Delete(int employeeKey)
        {
            using (var context = new CodeFirstModels())
            {
                var employee = context.Employees.Where(e => e.EmployeeKey == employeeKey).First();
                context.Employees.Remove(employee);
                context.SaveChanges();
            }
        }

實體框架(中級用戶)

可使用內嵌SQL避免資料庫的往返交互操作。

public void Delete(int employeeKey)
        {
            using (var context = new CodeFirstModels())
            {
                context.Database.ExecuteSqlCommand("DELETE FROM HR.Employee WHERE EmployeeKey = @p0", employeeKey);
            }
        }

投影(Projection)操作

投影是中間層開發中的一個重要部分。在取回了比實際所需更多的數據時,資料庫常會完全失去使用覆蓋索引或索引的能力,這將導致嚴重的性能影響。

Chain

同上,Chain將僅選取指定對象類型所需的所有列。

public IList<EmployeeOfficePhone> GetOfficePhoneNumbers()
        {
            return m_DataSource.From("HR.Employee").ToCollection<EmployeeOfficePhone>().Execute();
        }

Dapper

鑒於Dapper是顯式的,所以是由開發人員確保只選取必需的列。

public IList<EmployeeOfficePhone> GetOfficePhoneNumbers()
        {
            using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                return con.Query<EmployeeOfficePhone>("SELECT e.EmployeeKey, e.FirstName, e.LastName, e.OfficePhone FROM HR.Employee e").AsList();
            }
        }

實體框架

實體框架需要額外的操作步驟,這些步驟常因為有些繁瑣而被忽視。

通過在調用ToList前就包括了額外的選擇語句,實體架構可生成正確的SQL語句,並避免從資料庫返回過多的信息。

public IList<EmployeeOfficePhone> GetOfficePhoneNumbers()
        {
            using (var context = new CodeFirstModels())
            {
                return context.Employees.Select(e => new EmployeeOfficePhone()
                {
                    EmployeeKey = e.EmployeeKey,
                    FirstName = e.FirstName,
                    LastName = e.LastName,
                    OfficePhone = e.OfficePhone
                }).ToList();
            }
        }

使用投影做更新操作

固然,在存在投影對象時直接從投影對象更新資料庫是一種好的方法。該方法在Chain和Dapper的基本模式中是天然存在的。而在實體框架中,則必須要在手工拷貝屬性和編寫Dapper風格的內嵌SQL這兩種方法間做出選擇。

Chain

註意,任何未在投影類上具有匹配屬性的列將不受到影響。

public void Update(EmployeeOfficePhone employee)
        {
            return m_DataSource.Update("HR.Employee", employee).Execute();
        }

Dapper

public void Update(EmployeeOfficePhone employee)
        {
            const string sql = @"UPDATE HR.Employee
SET     FirstName = @FirstName,
        LastName = @LastName,
        OfficePhone = @OfficePhone
WHERE   EmployeeKey = @EmployeeKey
";
            using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                con.Execute(sql, employee);
            }
        }

實體框架

public void Update(EmployeeOfficePhone employee)
        {
            using (var context = new CodeFirstModels())
            {
                var entity = context.Employees.Where(e => e.EmployeeKey == employee.EmployeeKey).First();
                entity.FirstName = employee.FirstName;
                entity.LastName = employee.LastName;
                entity.OfficePhone = employee.OfficePhone;
                context.SaveChanges();
            }
        }

反射插入(Reflexive Insert)

現在我們來看一些更有意思的用例。反射插入意味著返回被插入的對象。做反射插入通常是為了獲得預設的和計算的域。

模型

註意,實體框架和Chain需要對屬性進行註釋,這樣庫才會知道該域將由資料庫予以設置。

[DatabaseGenerated(DatabaseGeneratedOption.Computed)] //Needed by EF
        [IgnoreOnInsert, IgnoreOnUpdate] //Needed by Chain
        public DateTime? CreatedDate { get; set; }

Chain

Chain允許將ToObject附加到任何插入或更新操作上。

public Employee InsertAndReturn(Employee employee)
        {
            return m_DataSource.Insert("HR.Employee", employee).ToObject<Employee>().Execute();
        }

Dapper

使用Dapper的反射插入,可以使用特定於資料庫的功能實現,例如OUTPUT語句。

public Employee InsertAndReturn(Employee employee)
        {
            const string sql = @"INSERT INTO HR.Employee
        (FirstName,
         MiddleName,
         LastName,
         Title,
         ManagerKey,
         OfficePhone,
         CellPhone
        )
    OUTPUT 
        Inserted.EmployeeKey,
        Inserted.FirstName,
        Inserted.MiddleName,
        Inserted.LastName,
        Inserted.Title,
        Inserted.ManagerKey,
        Inserted.OfficePhone,
        Inserted.CellPhone,
        Inserted.CreatedDate
VALUES  (@FirstName,
         @MiddleName,
         @LastName,
         @Title,
         @ManagerKey,
         @OfficePhone,
         @CellPhone
        );";
            using (var con = new SqlConnection(m_ConnectionString))
            {
                con.Open();
                return con.Query<Employee>(sql, employee).First();
            }
        }

如果一併考慮初學者級別模式,更典型的做法是僅在Get方法之後調用Insert方法。

public Employee InsertAndReturn_Novice(Employee employee)
        {
            return Get(Insert(employee));
        }

實體框架

使用前面提及的DatabaseGenerated屬性,你可以插入一個新的實體並讀回它的計算的和/或預設的列。

public Employee InsertAndReturn(Employee employee)
        {
            using (var context = new CodeFirstModels())
            {
                context.Employees.Add(employee);
                context.SaveChanges();
                return employee;
            }
        }

受限更新/局部更新

有時應用並沒有打算對每個列做更新,尤其是當模型是直接源自於UI並可能混合了可更新域和不可更新域時。

Chain

在Chain中,使用IgnoreOnInsert和IgnoreOnUpdate屬性去限制插入和更新操作。為允許用資料庫作為預設取值,典型的做法是將這兩個屬性都置於CreatedDate類型的列中。為避免更新操作過程中的意外改變,通常將IgnoreOnUpdate屬性置於CreatedBy之類的列上。

Dapper

就顯式編寫的插入和更新語句而言,Dapper最具靈活性。

實體框架

除了計算列(列值為表達式),實體框架並未給出一種簡單的方法可聲明某一列不參與插入或刪除操作,但可使用更新操作的“讀-拷貝-寫”(read-copy-write)模式模擬該行為。

更新或插入(Upsert)操作

經常需要作為一個單一操作完成記錄的插入或者更新,尤其是在使用自然主鍵(natural key)時。

Chain

在Chain中,Upsert操作的實現使用了與插入和刪除相同的設計。所生成的SQL隨資料庫引擎不同而各異(例如:SQL Server使用了MERGE,SQLit使用了一系列語句)。

public int Upsert(Employee employee)
        {
            return m_DataSource.Upsert("HR.Employee", employee).ToInt32().Execute();
        }

Dapper

在Dapper中,Upsert操作的實現需要多輪的來回交互,或是需要比較複雜的特定於資料庫的SQL語句。本文對此不作闡述。

實體框架

在實體框架中,這(過程?函數?都可以用“這”指代)僅作為被改進的更新操作的一個變體。

public int Upsert(Employee employee)
        {
            using (var context = new CodeFirstModels())
            {
                if(employee.EmployeeKey == 0)
                    context.Entry(employee).State = EntityState.Added;
                else
                    context.Entry(employee).State = EntityState.Modified;
                context.SaveChanges();
                return employee.EmployeeKey;
            }
        }

性能

雖然本文所採用的主要基準測試是代碼量和易用性,但是對實際性能的考慮也是非常有用的。

所有的性能基準測試中都包括了預熱過程,其後是對主迴圈做1000次迭代操作。每次測試中都使用了同樣的模型,模型使用實體框架的代碼優先(Code First)技術從資料庫代碼生成器產生。所有迭代都相當於共計13個基本CRUD操作,其中包括創建、讀取、更新和刪除操作。

我要澄清的是,這裡所做的僅是一些粗略的測試,使用了任何人在剛開始接觸這些庫時通常就會看到的代碼類型。當然一些高級技術可以改進每個測試的性能,有時甚至是極大地改進。

BenchmarkDotNet計時

  • Chain:平均3.4160毫秒,標準偏差為0.2764毫秒;
  • 未使用經編譯的物化器(Compiled Materializers)的Chain:平均3.0955毫秒,標準偏差0.1391毫秒;
  • Dapper:平均2.7250毫秒,標準偏差0.1840毫秒;
  • 實體框架(初學者):平均13.1078毫秒,標準偏差0.4649毫秒;
  • 實體框架(中級用戶):平均10.11498毫秒,標準偏差0.1952毫秒;
  • 實體框架(未使用AsNoTracking的中級用戶):平均9.7290毫秒,標準偏差0.3281毫秒。

結論

雖然可使用任何ORM框架去實現基本的倉儲模式,但是各種實現的性能和所需的代碼量具有顯著的差異。選取實現方式時需要對這些因素進行平衡,此外還需考慮資料庫可移植性、跨平臺支持和開發人員經驗等。 

在該系列文章的第二部分,我們將著眼於那些不僅將倉儲模式作為瘦抽象層的高級用例。

你可以在GitHub上獲取本文的代碼。

關於作者

Jonathan Allen的首份工作是在上世紀九十年代末做診所的MIS項目,Allen將項目逐步由Access和Excel升級到企業級的解決方法。在從事為財政部門編寫自動交易系統代碼的工作五年之後,他成為項目顧問,參與了包括機器人倉庫UI、癌症研究軟體中間層、主要房地產保險企業的大數據需求等在內的各種行業項目。在閑暇時間,他喜歡研究源於16世紀的武術,併為其撰寫文章。

 

查看英文原文:Implementation Strategies for the Repository Pattern with Entity Framework, Dapper, and Chain

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 使用橋接的鏈接方式,centos6.4配置靜態ip,能ping通網關,但ping 外網時出現 "network is unreachable" 如:ping www.baidu.com。 原來是路由配置問題。嘗試直接修改系統文件/etc/sysconfig/network-scripts/route ...
  • 安裝 squid yum install squid -y 備份squid.conf cp squid.conf squid.conf-list vi squid.conf 輸入: acl denyWeb dstdomain "/etc/squid/denyWeb.list" http_access ...
  • 伺服器添加3塊磁碟的體繫結構 [root@oldboylinux test]# free -m total used free shared buffers cached Mem: 992 133 859 0 24 37 -/+ buffers/cache: 70 921 Swap: 511 0 5... ...
  • 前面的話   在網上找了一些關於命令提示符CMD的資料,但是很多資料都是把所有的功能羅列出來,大部分都不會用到。所以,自己把常用的CMD命令總結如下,方便查閱   操作類 列出所有支持的指令及說明 ,如 說明cd命令的詳細用法 清屏 退出當前程式 使用ctrl+c快捷鍵 ...
  • Linux主要分為兩大系發行版,分別是RedHat和Debian,lamp環境的安裝和配置也會有所不同,所以分別以CentOS 7.1和Ubuntu 14.04做為主機(L) Linux下安裝軟體,最常見有源碼安裝方式、RPM/deb安裝方式、yum/apt get安裝方式等,在這裡使用yum/ap ...
  • 0、系統環境 0.1 此處選擇用戶較多的ubuntu作為入門系統,選擇U盤裝,在ubuntu官網https://www.ubuntu.com/download,選擇適合自己的ubuntu版本下載系統鏡像文件。 0.2 選擇容量大於4G的U盤 用Universal UBS installer 製作啟動 ...
  • 安裝並配置MySQL 5.6 從CentOS從7.x開始預設使用MariaDB。MariaDB完全相容MySQL,包括API和命令行。但是很多時候我們還是會想要安裝MySQL,所以不能直接通過yum命令安裝。 下載源安裝文件 本地安裝rpm包(配置MySQL安裝源) 查看所有MySQL安裝源(預設M ...
  • 一些常用的SQL語句大全參考:http://www.cnblogs.com/acpe/p/4970765.html 這篇博文整理的比較全,我摘抄一些基本常用的。 創建資料庫 CREATE DATABASE database-name 刪除資料庫 drop database dbname 創建新表 c ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...