문제

제네릭을 실험 중이고 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을 클래스로 변경하는 것입니다. 그러나 내 테스트에 따르면 메모리 사용량이 ~40%(각 개체 메타데이터로 인해) 증가하는 것으로 나타났습니다. 이는 허용되지 않습니다.

그래서 내 질문은 다음과 같습니다다른 구조화된 개체/레코드에서 작동할 수 있는 메서드를 만드는 방법에 대한 다른 아이디어가 있는 사람이 있습니까?F# 커뮤니티의 누군가가 기능적 언어에서 이러한 문제를 해결하는 방법과 이것이 성능 및 메모리 사용에 어떤 영향을 미치는지 제안할 수 있습니다.

편집하다:
sfg 매크로에 대한 제안에 감사드립니다.
Visual Studio 2008에는 T4라는 기본 제공(아직 알려지지 않은) 템플릿 엔진이 있습니다.요점은 내 프로젝트에 '.tt' 파일을 추가하고 내 모든 클래스를 검색하고 어떻게든 레코드인 클래스를 인식하고(예를 들어 구현하는 일부 인터페이스에 의해) HasChanges 및 AcceptChanges를 사용하여 부분 클래스를 생성하는 템플릿을 만드는 것입니다. ) 클래스에 포함된 열만 호출합니다.

유용한 링크:
VS용 T4 편집기
T4에 대한 링크와 튜토리얼이 포함된 블로그
EnvDTE를 사용하여 프로젝트 파일을 읽는 예제가 포함된 블로그 항목

도움이 되었습니까?

해결책

기능 언어의 예제를 요청하면서; LISP에서는 매크로를 사용하여 코드를 크랭크하여 열이 추가 될 때 모든 코드를 쓰는 것을 방지 할 수 있습니다. 슬프게도, 나는 그것이 C#에서 가능하다고 생각하지 않습니다.

성능 측면에서 : 매크로는 컴파일 타임에 평가 될 것이지만 (따라서 컴파일 속도는 적은 양으로) 런타임 코드가 수동으로 작성하는 것과 동일하기 때문에 런타임에 속도가 느려지지 않습니다.

클로닝 된 버전에 쓰기를 피하려면 식별자에 의해 Structs에 직접 액세스해야하므로 원래 수락 체인지 ()를 수락해야 할 수도 있다고 생각합니다.

다시 말해서 : 당신은 당신을 위해 프로그램을 작성하는 프로그램이 필요하며, 나는 structs를 수업으로 전환함으로써 당신이했던 것보다 특별한 길이를 가지거나 성능을 잃지 않고 C#에서 그것을하는 방법을 모릅니다 (예 : 반사). .

다른 팁

반사를 사용하여 멤버들을 반복하고 해임스와 수락 체인을 호출 할 수 있습니다. 레코드 클래스는 반사 메타 데이터를 정적으로 저장할 수 있으므로 인스턴스 당 메모리 오버 헤드가 없습니다. 그러나 런타임의 성능 비용이 크게 될 것입니다. 또한 비용에 더 많은 추가로 추가 된 열을 복싱하고 발행 할 수도 있습니다.

솔직히, 당신이 정말로 이것을 원한 것 같습니다 Column수업이지만 클래스와 관련된 런타임 비용을 지불하고 싶지 않으므로 구조를 만들려고합니다. 나는 당신이 원하는 것을 할 수있는 우아한 방법을 찾을 것이라고 생각하지 않습니다. 스트러크는 가치 유형이어야하며, 참조 유형처럼 행동하게 만들고 싶습니다.

열을 배열에 효율적으로 저장할 수 없습니다. IColumnS, 따라서 배열 접근 방식은 잘 작동하지 않습니다. 컴파일러는 그것을 알 수있는 방법이 없습니다 IColumn 배열은 구조 만 고정 할 것이며, 실제로, 당신이 거기에 잼을 시도하는 다른 유형의 스트러크가 여전히 있기 때문에 그것이 도움이되지 않을 것입니다. 누군가 전화 할 때마다 AcceptChanges() 또는 HasChanges(), 당신은 어쨌든 권투와 복제를 끝낼 것입니다. 그래서 나는 당신의 Column 클래스 대신 구조물은 많은 기억을 절약 할 것입니다.

그러나 당신은 ~할 수 있었다 아마 당신의 저장 Column배열에 직접 s를하고 열거로 인덱싱합니다. 예 :

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 ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top