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

时间:2016-07-17 23:18:00

标签: 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" },
        }

    },

};

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

IReportModel ReportModel = Template.CreateReport();

ReportModel.Get(filters);

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);

}

但每次执行方法ExtractFiltersByAType时,abfilters的值都会更改为等于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;
}

这是我的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;
}

问题

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

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

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

我认为filterSource.SubFilters = ExtractFiltersByAType(filterSource.SubFilters, IsStandard);中的ExtractFiltersByAType行引起了问题,但我不明白为什么以及如何做。

2 个答案:

答案 0 :(得分: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; }
}

使用ref

如果您想要更改来电者的对象参考,则需要传递使用该用户时使用的实际参考实际参考 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; }
}

您的案例

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

filterSource.SubFilters = ExtractFiltersByAType(filterSource.SubFilters, IsStandard);

此行实际上更改了此对象的子过滤器。

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

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

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

    return copyOfFilters;
}

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

以下是发生事件的一个例子。

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;
    }

我建议在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;
}

关于实现ICloneable接口,请参考以下问题: Proper way to implement ICloneable

修改

正如用户muratgu在问题评论中所提到的,您的CloneFilter方法正在执行列表的shallow copy,您要查找的是deep copy。这可以通过上述ICloneable接口实现。

答案 1 :(得分:0)

ref唯一能做的就是确定接收参数的方法是否可以修改传递给它的变量。对于对象,这意味着将变量设置为不同的对象或将其设置为null

但即使您不使用ref,您将参数传递给的方法也可以设置其属性或调用修改该对象状态的方法。这是正常的和预期的。将对象传递给另一个方法时,其他方法不仅可以读取其属性。您可能还希望该方法以某种修改它的方式对该对象进行操作。

最简单的例子是List<T>。如果您将List<string>传递给另一个方法 - 不使用ref - 其他方法可以将项目添加到列表中,请修改列表中的项目,清除列表等。

使用ref的唯一区别是,如果传递变量的方法将变量设置为不同的列表或将其设置为null,那么它将修改方法中的变量通过了论证。

大多数时候我们不希望我们调用的方法完全替换我们传入的变量。如果我们想要一个新对象,我们会编写一个返回一个对象的函数,而不是一个替换我们传递的变量的函数。 in。