[英]Is it possible to avoid boxing when setting C# struct/class fields using reflection?
[英]class with valueTypes fields and boxing
我正在嘗試泛型,並且試圖創建類似於Dataset類的結構。
我有以下代碼
public struct Column<T>
{
T value;
T originalValue;
public bool HasChanges
{
get { return !value.Equals(originalValue); }
}
public void AcceptChanges()
{
originalValue = value;
}
}
public class Record
{
Column<int> id;
Column<string> name;
Column<DateTime?> someDate;
Column<int?> someInt;
public bool HasChanges
{
get
{
return id.HasChanges | name.HasChanges | someDate.HasChanges | someInt.HasChanges;
}
}
public void AcceptChanges()
{
id.AcceptChanges();
name.AcceptChanges();
someDate.AcceptChanges();
someInt.AcceptChanges();
}
}
我遇到的問題是,當我添加新列時,我還需要在HasChanges屬性和AcceptChanges()方法中添加它。 這只是要求一些重構。
所以我想到的第一個解決方案是這樣的:
public interface IColumn
{
bool HasChanges { get; }
void AcceptChanges();
}
public struct Column<T> : IColumn {...}
public class Record
{
Column<int> id;
Column<string> name;
Column<DateTime?> someDate;
Column<int?> someInt;
IColumn[] Columns { get { return new IColumn[] {id, name, someDate, someInt}; }}
public bool HasChanges
{
get
{
bool has = false;
IColumn[] columns = Columns; //clone and boxing
for (int i = 0; i < columns.Length; i++)
has |= columns[i].HasChanges;
return has;
}
}
public void AcceptChanges()
{
IColumn[] columns = Columns; //clone and boxing
for (int i = 0; i < columns.Length; i++)
columns[i].AcceptChanges(); //Here we are changing clone
}
}
從注釋中可以看出,這里的結構克隆問題很少。 一個簡單的解決方案是將Column更改為class,但是從我的測試看來,它使內存使用量增加了約40%(由於每個對象元數據),這對我來說是不可接受的。
所以我的問題是:是否有人對如何創建可在不同結構化對象/記錄上使用的方法有其他想法? 也許來自F#社區的人可以建議如何使用功能語言解決此類問題,以及如何影響性能和內存使用。
編輯:
sfg感謝您對宏的建議。
在Visual Studio 2008中,有一個稱為T4的內置(但未知)模板引擎。 整個過程就是將“ .tt”文件添加到我的項目中,並創建一個模板來搜索我的所有類,以某種方式識別記錄(例如通過它們實現的某些接口),並使用HasChanges和AcceptChanges( ),將僅調用該類包含的列。
一些有用的鏈接:
VS的T4編輯器
有關T4的鏈接和教程的博客
帶有使用EnvDTE讀取項目文件的示例的博客條目
如您所要求的功能語言示例; 簡而言之,您可以使用宏為您修改代碼,從而避免在每次添加列時編寫所有代碼。 可悲的是,我認為這在C#中是不可能的。
就性能而言:宏將在編譯時進行評估(因此會使編譯速度變慢),但不會在運行時導致運行速度降低,因為運行時代碼與您手動編寫的代碼相同。
我認為您可能必須接受原始的AcceptChanges(),因為如果要避免寫入克隆的版本,則需要直接通過其標識符訪問結構。
換句話說:您需要一個程序來為您編寫該程序,而且我不知道如何在C#中做到這一點,而又不會花費過多的時間或通過將結構切換到類(例如反射)而比以前損失更多的性能。
您可以使用反射來遍歷成員並調用HasChanges和AcceptChanges。 Record類可以將反射元數據存儲為靜態,因此不存在每個實例的內存開銷。 但是,運行時的性能成本將是巨大的-您可能最終還會對列進行裝箱和拆箱,這會增加更多的成本。
老實說,聽起來您確實希望這些Column
成為類,但是不想支付與類相關的運行時成本,因此您正在嘗試使它們成為結構。 我認為您不會找到一種優雅的方式來做自己想要的事情。 結構應該是值類型,您想使它們的行為類似於引用類型。
您無法將Columns有效地存儲在IColumn
的數組中,因此沒有一種數組方法可以很好地工作。 編譯器無法知道IColumn
數組僅保留結構,並且確實做到了,也無濟於事,因為您仍然嘗試在其中IColumn
不同類型的結構。 每當有人調用AcceptChanges()
或HasChanges()
,無論如何都要結束裝箱並克隆結構,因此我嚴重懷疑使Column
成為結構而不是類會節省大量內存。
但是,您可能可以將Column
直接存儲在數組中,並使用枚舉對其進行索引。 例如:
public class Record
{
public enum ColumnNames { ID = 0, Name, Date, Int, NumCols };
private IColumn [] columns;
public Record()
{
columns = new IColumn[ColumnNames.NumCols];
columns[ID] = ...
}
public bool HasChanges
{
get
{
bool has = false;
for (int i = 0; i < columns.Length; i++)
has |= columns[i].HasChanges;
return has;
}
}
public void AcceptChanges()
{
for (int i = 0; i < columns.Length; i++)
columns[i].AcceptChanges();
}
}
我沒有C#編譯器,因此無法檢查它是否行得通,但是即使我沒有正確理解所有細節,但基本思想還是行得通。 但是,我只是繼續讓他們上課。 您還是要為此付費。
我能想到要做的唯一方法就是使用Reflection。 仍然可以裝箱/拆箱,但是可以讓您將克隆存儲回字段中,從而有效地使它成為真正的價值。
public void AcceptChanges()
{
foreach (FieldInfo field in GetType().GetFields()) {
if (!typeof(IColumn).IsAssignableFrom(field.FieldType))
continue; // ignore all non-IColumn fields
IColumn col = (IColumn)field.GetValue(this); // Boxes storage -> clone
col.AcceptChanges(); // Works on clone
field.SetValue(this, col); // Unboxes clone -> storage
}
}
這個怎么樣:
public interface IColumn<T>
{
T Value { get; set; }
T OriginalValue { get; set; }
}
public struct Column<T> : IColumn<T>
{
public T Value { get; set; }
public T OriginalValue { get; set; }
}
public static class ColumnService
{
public static bool HasChanges<T, S>(T column) where T : IColumn<S>
{
return !(column.Value.Equals(column.OriginalValue));
}
public static void AcceptChanges<T, S>(T column) where T : IColumn<S>
{
column.Value = column.OriginalValue;
}
}
客戶端代碼如下:
Column<int> age = new Column<int>();
age.Value = 35;
age.OriginalValue = 34;
if (ColumnService.HasChanges<Column<int>, int>(age))
{
ColumnService.AcceptChanges<Column<int>, int>(age);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.