C#保持方法中的更改超出范围

时间:2011-11-04 04:45:17

标签: c#

仍然是C#编程的初学者 - 非常感谢可能是一个基本问题的帮助。

我在应用程序中有许多实现公共接口的对象。我想创建一个方法,它将接受实现该接口的任何对象集合,对这些对象执行操作,原始集合已被转换。

我看到的行为是在调用代码外部的方法范围内操作对象,而不是在调用之前和之后操作。我无法弄清楚如何解决这个问题。

以下是我使用的测试代码的简化版本。

界面如下:

public interface IDated
{
    DateTime? Date { get; set; }
}

实现该接口的对象示例:

public class MyObject : IDated
{
    public string Name { get; set; }
    public DateTime? Date { get; set; }
}

更改对象的方法:

public class Operator
{
    public void Operate(IEnumerable<IDated> objects)
    {
        objects = from o in objects where o.Date.HasValue select o;
    }
}

调用代码,最后没有发生任何变化:

static void Main(string[] args)
    {
        var one = new MyObject("One", null);
        var two = new MyObject("Two", DateTime.Parse("30-06-06"));

        var list = new List<MyObject>();
        list.Add(one);
        list.Add(two);

        new Operator().Operate(list);

        Console.WriteLine(String.Format("Objects in list = {0}\r\nOperation Succeeded: {1}", list.Count.ToString(), (list.Count == 1).ToString()));
    }

输出为“2”和“False”。谷歌搜索这个问题,我已尝试将集合作为“ref”参数传递,如:

public void Operate(ref IEnumerable<IDated> objects)
    {
        objects = from o in objects where o.Date.HasValue select o;
    }

new Operator().Operate(ref list);

然而,最后一点代码有一个漂亮的鲜红色下划线,并认为该参数无效。我无法理解为什么?

再一次,任何帮助都非常感激。

干杯,

6 个答案:

答案 0 :(得分:3)

您的Operate功能实际应该返回您的新列表。这是将新列表恢复到调用函数的唯一方法。

请注意,这意味着您不能说:

var list = new List<MyObject>();
list = new Operator().Operate(list);

你要么这么说:

var list = new List<MyObject>();
list.Add(one);
list.Add(two);
var output = new Operator().Operate(list);

或者这个:

IEnumerable<IDated> list = new List<MyObject> { one, two };
list = new Operator().Operate(list);

答案 1 :(得分:1)

创建一个使用yield return的扩展方法(当然,你将返回一个枚举,而不是修改枚举本身,这是你在使用枚举时应该做的事情。)

public static class IDatedExtensions
{
    public static IEnumerable<T> HasDate(IEnumerable<T> objects)
        where T : IDated
    {
        foreach (var obj in objects)
        {
            if ((obj != null) && (obj.Date.HasValue))
                yield return obj;
        }
    }
}

用法:

public void Foo()
{
    var list = new List<MyObject>();
    list.Add(one);
    list.Add(two);

    foreach (var obj in list.HasDate())
    {
        //You should really have this be culture aware since you're working
        //with DateTime...
        Console.WriteLine(String.Format(CultureInfo.CurrentCulture,
            "{0}: {1}", obj.Name, obj.Date.Value));
    }
}

public void Foo2()
{
    var list = new List<MyObject>();
    list.Add(one);
    list.Add(two);

    //Use LINQs ToList() to get a new List<T>
    var newList = list.HasDate().ToList();

    ...
}

哦,旁注:

Console.WriteLine(string format, params Object[] args)已经为你格式化了字符串,除非你打算提供IFormatProvider(在你的情况下你应该这样做,但你没有),否则String.Format是不必要的......

我还应该提一下,虽然这和被问到的不一样,但我想我会把这个答案扔出去,因为如果你是C#的新手,你也可以从右脚开始。不要修改您正在迭代的集合。使用枚举从yield返回枚举中的对象。

答案 2 :(得分:0)

您可能需要考虑使用扩展方法来构建管道。您的代码将类似于:

public static DatedSetExtensions
{
    public static IEnumerable<IDated> WhereHasDate(this IEnumerable<IDated> input)
    {
        return input.Where(x => x.Date.HasValue);
    }
}

...

list = list.WhereHasDate();

请注意,IEnumerable与列表有很大不同。 IEnumerable只知道如何遍历某个集合。它实际上并不存储或知道如何操纵它。

如果您希望原始代码对给定集合进行操作,则应为其提供正确的类型,即:集合,如:

    public void FilterByHasDate(this Collection<IDated> input)
    {
        var itemsToRemove = input.Where(x => x.Date.HasValue).ToArray();
        foreach (var itemToRemove in itemsToRemove)
        {
            input.Remove(itemToRemove);
        }
    }

编辑我现在假设您希望扩展方法返回输入集合中元素的原始类型。您可以使用泛型和类型约束轻松管理它,例如:

class Foo {}
class Bar : Foo {}

static class FooExtensions
{
    public static IEnumerable<T> WhereSomething<T>(this IEnumerable<T> input) where T: Foo
    {
        return input;
    }
}

....

List<Bar> bars = new List<Bar>();
IEnumerable<Bar> filteredBars = bars.WhereSomething();

这里我们有一个操作员在Foo集合上工作但仍然返回一个条形集合,就像我们传入它的那样。感谢仿制药

答案 3 :(得分:0)

因此,如果在Operator.Operate方法中执行writeline语句,您将获得预期的结果。我认为来自IList&lt; MyObject&gt;的隐式演员表。到IEnumerable&lt; IDated&gt;创建一个临时变量,该变量仅在方法调用期间的范围内。

将代码更改为此(使用简洁语法)

static void Main(string[] args)
        {
            var one = new MyObject("One", null);
            var two = new MyObject("Two", DateTime.Parse("06-30-06"));

            var list = new List<IDated> {one, two};

            new Operator().Operate(ref list);

            Console.WriteLine(String.Format("Objects in list = {0}\r\nOperation Succeeded: {1}", list.Count.ToString(), (list.Count == 1).ToString()));
            Console.ReadKey();
        }

public class Operator
{
    public void Operate(ref List<IDated> objects)
    {
        objects = objects.Where(o => o.Date.HasValue).ToList();
        Console.WriteLine(String.Format("Objects in list = {0}\r\nOperation Succeeded: {1}", objects.Count(), objects.Count() == 1));
    }
}

你得到了预期的结果。如果不使用ref关键字装饰参数,则当方法超出范围时,列表会丢失它的更改。我不确定为什么因为这些都是引用类型,所以我认为如果不涉及转换,我们会看到主程序中的更改。

答案 4 :(得分:0)

如果您专注于使用定义为:

的运算符来查找解决方案
public void Operate(IEnumerable<IDated> objects)
你显然不会找到一个因为方法没有返回任何东西,并且IEnumerable参数不能从inside方法中修改以致任何人在外面注意到它的效果。 (在方法内部没有一些智能强制转换为List<T>并且无论如何都要调用Clear()

如果你想真正修改输入参数,你可以拥有它

public void Operate(List<IDated> objects)

现在在内部操作中,您可以调用objects.Remove()来匹配对象。

调用此void方法后,实际列表将被修改。

当您尝试将List<T>变量分配给方法中的ref IEnumerable<T>时,将List<T>作为IEnumerable<T>传递的方法注定要失败,只需将其作为{{1}传递给ref List<T> }如果你真的喜欢ref方法(我没有)或者通过在生成的IEnumerable上调用List<T>将其分配给.ToList()

答案 5 :(得分:0)

您的大多数代码都没问题,但这些代码行不起作用:

public class Operator
{
    public void Operate(IEnumerable<IDated> objects)
    {
        objects = from o in objects where o.Date.HasValue select o;
    }
}

这种方法存在一些问题:

  • 分配正常的非ref方法参数不会更改函数外的任何内容
  • IEnumerable用于读取数据,而不是(直接)修改数据

方法参数类似于其他临时变量

以下是一个示例,说明重新分配objects的原因无效。它与您已经拥有的代码非常相似:

public class SomeClass
{
    public void Something()
    {
        List<int> values = new List<int> { 1, 2, 3, 4, 5 };
        IEnumerable<int> valuesParam = values;
        valuesParam = values.Where(i => i < 3);

        // Will print 1 through 5, not 1 through 2...
        foreach(int value in values)
        {
            Console.WriteLine(value);
        }
    }
}

问题在于,当你调用一个方法时,你正在制作另一个引用你的初始变量的变量。然后,当您重新分配给它时,您只需重新分配该临时变量。当临时变量消失时,您的更改也会消失。

IEnumerable用于只读访问

您会注意到IEnumerable不是您可以修改的内容。您Add无法RemoveIEnumerable。因此,您必须执行某种分配才能使Operate方法正常工作。

使用ref

您尝试使用ref解决此问题,但这不起作用,因为您传入的值是List<IDated>,而不是IEnumerable<IDated>。类型基本上必须匹配。

你可以解决这个问题,但ref一般使用起来会有点痛苦,所以我不推荐它......

使用返回值

您可以通过将过滤后的列表作为返回值传回来重新分配列表的原始值来解决此问题。由于您选择IEnumerable<IDated>,我建议您也返回一个,并在结果上使用ToList()方法将其重新设置为与原始列表相同的类型。

list = new Operator()
    .Operate(list)
    .ToList(); // Note: Important!

// ...

public IEnumerable<IDated> Operate(IEnumerable<IDated> objects)
{
    return objects.Where(o => o.Date.HasValue);
}