为什么我的方法会更改传递给它的对象的原始值?

Jay*_*len 1 c#

我有以下对象

var filters = new List<IReportFilter>
{
    new ReportFilter
    { 
        ReportColumn = new ReportColumn{ ColumnKey = "Result.IsCompleted"},
        Value = "1",
        SubFilters = new List<IReportFilter> 
        {
             new ReportFilter { SqlOperator = FilterOperator.Or, ReportColumn = new ReportColumn{ ColumnKey = "User.LastName"}, Value = "Alhayek"},
             new ReportFilter { SqlOperator = FilterOperator.Or, ReportColumn = new ReportColumn{ ColumnKey = "User.LastName"}, Value = "Smith"},
             new ReportFilter { SqlOperator = FilterOperator.Or, ReportColumn = new ReportColumn{ AggregateFunction = SqlAggregateFunctions.Count}, Type = FilterType.GreaterThenOrEqualTo ,Value = "0" },
        }

    },

};
Run Code Online (Sandbox Code Playgroud)

obove对象使用类似的方法传递给另一个类

IReportModel ReportModel = Template.CreateReport();

ReportModel.Get(filters);
Run Code Online (Sandbox Code Playgroud)

在类的Get方法内部,ReportModel我想循环遍历filters列表并创建新列表而不更改原始列表.新列表将成为原始列表的子集.

从我的Get方法来看,这就是我所做的

public SqlCommand Build(List<IReportFilter> filters)
{

    var a = CloneFilters(filters);

    var b = CloneFilters(filters);

    List<IReportFilter> standardFilters = ExtractFiltersByAType(a, true);

    List<IReportFilter> aggregateFilter = ExtractFiltersByAType(b, false);

}
Run Code Online (Sandbox Code Playgroud)

但每次我执行方法ExtractFiltersByAType的价值a,b以及filters改变等于相同的值aggregateFilter.

我不希望任何变量发生变化.但他们出于某种原因我不明白.

这是我的CloneFilters方法

private List<IReportFilter> CloneFilters(List<IReportFilter> myFilters)
{
    List<IReportFilter> copyOfFilters = new List<IReportFilter>();

    foreach (var myFilter in myFilters)
    {
        copyOfFilters.Add(myFilter);
    }

    return copyOfFilters;
}
Run Code Online (Sandbox Code Playgroud)

这是我的 ExtractFiltersByAType

private List<IReportFilter> ExtractFiltersByAType(List<IReportFilter> filtersSource, bool IsStandard = true)
{
    List<IReportFilter> validFilters = new List<IReportFilter>();

    foreach (var filterSource in filtersSource)
    {

        if (filterSource.SubFilters != null && filterSource.SubFilters.Any())
        {
            filterSource.SubFilters = ExtractFiltersByAType(filterSource.SubFilters, IsStandard); //I think this what could be causing this problem
        }

        if ((IsStandard && !filterSource.ReportColumn.IsAggregate) || (!IsStandard && filterSource.ReportColumn.IsAggregate))
        {
            validFilters.Add(filterSource);
        }

    }

    return validFilters;
}
Run Code Online (Sandbox Code Playgroud)

由于我不是ref通过引用方法来传递对象,为什么我的函数将值更改为原始对象?

将对象列表传递给c#中的方法时,系统是否会创建副本,还是会通过引用传递对象?

我怎样才能解决这个问题,以便每次执行ExtractFiltersByAType方法时,只更改副本而不是原件?

我在想,行filterSource.SubFilters = ExtractFiltersByAType(filterSource.SubFilters, IsStandard);ExtractFiltersByAType是造成问题,但我不明白为什么,以及如何.

And*_*aio 5

没有参考

将引用类型作为参数(包括列表)传递时,将引用的副本传递给该对象.这意味着您可以更改对象属性,但不能更改对象本身.

例:

public class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo(1);
        Console.WriteLine(foo.Bar);

        // This will change foo.Bar
        ChangeFoo(foo, 5); 
        Console.WriteLine(foo.Bar);

        // Does not change foo
        DoesNotChangeFoo(foo, 10);
        Console.WriteLine(foo.Bar);

        Console.Read();
    }

    static void ChangeFoo(Foo foo, int newValue)
    {
        // Since it receives a copy of the reference to Foo, it actually changes foo.Bar value
        foo.Bar = newValue;
    }

    static void DoesNotChangeFoo(Foo foo, int newValue)
    {
        // Since it receives a copy of the reference to foo, it only updates this method's reference, not changing the caller's reference
        foo = new Foo(newValue);
    }
}

public class Foo
{
    public Foo(int bar)
    {
        Bar = bar;
    }

    public int Bar { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

随着参考

如果要更改调用者的对象引用,则需要传递calle使用的实际引用,即使用ref关键字时.

例:

public class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo(1);
        Console.WriteLine(foo.Bar);

        // This will change foo's object reference
        ChangeFooObjectReference(ref foo, 15); 
        Console.WriteLine(foo.Bar);

        Console.Read();
    }

    static void ChangeFooObjectReference(ref Foo foo, int newValue)
    {
        // SInce you are receiving the actual reference used by the caller, you actually change it's own reference
        foo = new Foo(newValue);
    }
}

public class Foo
{
    public Foo(int bar)
    {
        Bar = bar;
    }

    public int Bar { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

你的情况

正如您所假设的那样,问题的主要原因是这一行:

filterSource.SubFilters = ExtractFiltersByAType(filterSource.SubFilters, IsStandard);
Run Code Online (Sandbox Code Playgroud)

这一行实际上改变了这个对象的SubFilters.

但是值得注意的是,克隆方法可能会遇到一些更大的问题.

private List<IReportFilter> CloneFilters(List<IReportFilter> myFilters)
{
    List<IReportFilter> copyOfFilters = new List<IReportFilter>();

    foreach (var myFilter in myFilters)
    {
        copyOfFilters.Add(myFilter);
    }

    return copyOfFilters;
}
Run Code Online (Sandbox Code Playgroud)

此方法返回一个新的List,但该列表的内容与参数的内容完全相同.这意味着,如果更改用作参数的对象中包含的任何对象,则也可以在新列表中更改它.

这是一个正在发生的事情的例子.

static void Main(string[] args)
    {
        List<Foo> foos = new List<Foo>();
        foos.Add(new Foo(2));

        List<Foo> newFoo = CreateNewFoo(foos);
        Console.WriteLine(newFoo.First().Bar);

        foos.First().Bar = 5;
        // Since we changed the first object of the old list, and it is the same object in the new list, we will get the new result.
        Console.WriteLine(newFoo.First().Bar);

        Console.Read();

    }

    static List<Foo> CreateNewFoo(List<Foo> foos)
    {
        List<Foo> newFoos = new List<Foo>();

        foreach(Foo foo in foos)
        {
            newFoos.Add(foo);
        }

        return newFoos;
    }
Run Code Online (Sandbox Code Playgroud)

我建议在IReportFilter接口中实现ICloneable接口,以及实现IReportFilter的每个具体类.ICloneable实现了一个方法Clone(),它返回一个对象.此方法应该创建一个实现它的同一个类的新实例,其中包含一个与当前对象相同的新对象.比你将你的方法更改为:

private List<IReportFilter> CloneFilters(List<IReportFilter> myFilters)
{
    List<IReportFilter> copyOfFilters = new List<IReportFilter>();

    foreach (var myFilter in myFilters)
    {
        copyOfFilters.Add(myFilter.Clone() as IReportFilter);
    }

    return copyOfFilters;
}
Run Code Online (Sandbox Code Playgroud)

至于实现ICloneable接口,请参考这个问题: 实现ICloneable的正确方法

编辑

正如用户muratgu在问题评论中所提到的,您的CloneFilter方法正在对列表进行浅层复制,您要查找的是深层复制.这可以通过上述ICloneable接口实现.