对象.GetObject(i)相比于objects[i]有什么优势吗?(涉及IT技术)

6

我正在重构一些以前开发人员的C#数据访问代码,并对他使用的模式感到好奇。

该代码最初公开了各种ActiveRecord风格的业务对象的集合(数组) - 本质上是包装数据库字段的对象。 我正在将这些数组更改为通用列表,但我好奇的代码方面是以前的开发人员为其包装的每种类型的对象编写了Get方法,如下所示:

public Thing GetThing(int i) {
    return things[i];
}

有几种方法可以实现这个功能,但我无法想出使用该机制相对于直接引用things [i] 的任何可能优势。假设为了论证而说,things是一个公共属性,而不是一个公共字段(在这种情况下,它实际上是自动实现的属性,因此这个假设实际上是正确的)。
我是否错过了一些明显的东西?或者是一些深奥的东西?
更新 我应该澄清一下,这些集合目前是从for循环中访问的:
for (int i = 0; i < thingsCount; i== ) {
    dosomthing( GetThing(i) );
    dosomethingelse( GetThing(i) );
}

我正在重构的代码:

for (int i = 0; i < thingsCount; i== ) {
    Thing thing = things[i];
    dosomthing( thing );
    dosomethingelse( thing );
}

还可以使用 things.foreach()。

5个回答

6
我不知道是否显而易见,但我认为你错过了一些东西。
假设`things`是一个`IList`,那么直接将其暴露(作为`Things`)将允许调用代码调用`Add`、`Insert`、`RemoveAt`等方法。也许之前的开发人员不想允许这样做(我相信有很多很好的理由)。
即使它是一个`Thing[]`(因此`Add`等方法不可用),将其公开仍将允许调用代码执行类似于`obj.Things[0] = new Thing();`的操作,这可能是根据类的实现不应该允许的操作。
你可以将`Things`公开为`ReadOnlyCollection`,这将解决大部分问题。但归根结底,如果开发人员只想允许调用代码按索引访问项——仅此而已——那么提供单个`GetThing`方法作为实现这一目的的手段,老实说,是最合理的选择。
当然,还有这个选项:实现一个只有`get`访问器的`this[int]`属性。但只有当涉及的类基本上是`Thing`对象的集合时(即,没有其他类型的对象集合你想在类中提供访问权限),这才有意义。
总的来说,我认为`GetThing`方法的方式非常可靠。
话虽如此,从你提问的方式来看,似乎之前的开发人员做了一些相当糟糕的决定:
1. 如果他/她还将`things`集合直接公开为公共属性,那么...这不是毁掉`GetThing`方法的全部目的吗?结果只是一个膨胀的接口(我通常认为,如果你有多个方法来完成完全相同的事情,除非它们明确地被记录为某些可以证明的原因的别名,否则这并不是一个很好的迹象)。(更新:看起来之前的开发人员没有这样做。很好。)
2. 看起来之前的开发人员还在使用`GetThing`方法从`things`中内部访问项,这很愚蠢(在我看来)。为什么要在类本身内部使用类的公共接口引入额外的方法调用开销?如果你在类中,你已经在实现内部了,可以随意访问私有/受保护的数据——没有必要假装否认。

通过阅读@Elpezmuerto的回答,我也得出了这个结论 - 即使我将collections公开为带有{get; private set;}的属性,一旦我获取了该集合,我可以在调用代码中以毫无意义的方式进行修改。 之前的开发人员将它们公开为受保护的,所以你提到的第1点在我的情况下不相关,但是他在每个循环中多次调用GetThing(i),我认为这毫无意义。 似乎最好的重构方法是保持GetThing()方法不变,但只在循环中调用一次。 谢谢! - cori
@cori:是的,开发人员在设计类时经常犯的一个错误是直接暴露可变集合,而没有考虑到如果/当这些集合被修改会发生什么。如果您只想允许调用代码枚举而不修改集合,请暴露 IEnumerable<T> 而不是例如 List<T> - Dan Tao
@cori:我承认,当你想通过索引允许随机访问,但不需要IList<T>接口或甚至是T[]的所有其他功能(它允许你设置项,而不仅仅是获取它们)时,这确实很令人沮丧。这实际上是我在一段时间前在我的一个旧问题中询问的内容。 - Dan Tao
@StriplingWarrior:我同意;然而唯一的问题在于,.NET框架中没有构建这样的接口(这基本上是我在先前评论中链接的问题的关键),这意味着返回该接口的实例除了允许您在将来的某个地方以不同的方式实现接口之外,并没有太多好处(但是如果您只公开一个“GetThing”方法,那么您也可以这样做)。 - Dan Tao
你应该尽量避免将集合暴露为可修改的属性或字段(通常情况下,字段或属性并不重要,除非你正在编写一个库,该库可以在不重新编译库用户的情况下更新 - 这种情况很少见)。据我所知,几乎没有理由优先选择List作为属性类型,而有几个理由优先选择数组而不是List - 或者更好的是,选择一些更干净的东西。基本上,任何属性最多应该是以下之一:可设置的、可变返回类型的或虚拟的。 - Eamon Nerbonne
显示剩余3条评论

2
您可能希望将该对象公开为 IList<Thing>。这样可以获得所需的索引功能,同时还可以使用 LINQ 函数范围,例如根据条件创建新项目列表。此外,IList<T> 实现了 IEnumerable<T>,因此您将能够使用 foreach 循环遍历对象。
例如,在您的类中:
public IList<Thing> Things { get; private set; }

例如用法:
Thing x = business.Things[3];

或者

var x = business.Things.Where(t => t.Name == "cori");

这正是我通过重构到List想要达到的目的(是的,IList可能是更好的选择)。 - cori

2
这里有两点需要注意。首先,您希望将对象变量保持为私有,并使用getter和setter来访问它们。这可以防止用户意外更改或修改对象变量。
其次,一般认为在直接访问属性的情况下必须使用get/set这些术语作为命名约定。这有助于提高可读性。
尽管在这种情况下是公共属性,但使用getter和setter可以提高可读性并防止意外行为。无论您是否在循环中访问对象变量,都应继续使用GetThing约定。
最后,如果您从对象内部访问变量,则不需要使用getter或setter。
注意:通常认为将对象变量保持为私有并为所有语言使用getter/setter是良好的风格。 C++样式指南 C#样式指南 您可能也对 "C#中类中的getter和setter" 这个问题感兴趣。

我能理解那个逻辑(尽管我在你指向的C#示例中没有看到任何引用它的东西)。谢谢! - cori

1
如果我必须猜的话,我会说这个开发者习惯于像Java这样的其他语言,并没有完全意识到C#的标准做法。在Java、Javascript等语言中,“get [Property]”命名法非常常用。C#用属性和索引器替换了这种方法。属性与getter和setter一样强大,但更易于编写和使用。你通常只在以下情况下在C#中看到“Get [something]”:
  • 该操作可能足够昂贵,以至于您确实希望强调这不是简单的成员访问(例如GetPrimeNumbers());或者
  • 您的集合实际上包括多个索引集合(例如GetRow(int i)GetColumn(int i)))。即使在这种情况下,更常见的做法也是将每个这些索引集合公开为一个索引类型的属性(“table.Rows[2]”)。

如果您只在for循环中访问这些值,则集合应实现IEnumerable<Thing>,这将使您可以访问LINQ方法和foreach结构。如果您仍然需要具有基于索引的getter,则应考虑使用自己的接口,该接口扩展了IEnumerable<T>,但还提供了:

T this[int i] { get; }

这样,您就不会给消费者留下他们可以在此集合中添加删除对象的印象。

更新

我知道这主要是一种风格问题,因为这是有争议的,但我真的认为GetThings解决方案不是正确的做法。以下策略虽然需要更多的工作,但更符合标准.NET类和框架的设计方式:

public class ThingHolderDataAccess
{
    public ThingHolder GetThingHolderForSomeArgs(int arg1, int arg2)
    {
        var oneThings = GetOneThings(arg1);
        var otherThings = GetOtherThings(arg2);
        return new ThingHolder(oneThings, otherThings);
    }
    private IEnumerable<OneThing> GetOneThings(int arg)
    {
        //...
        return new List<OneThing>();
    }
    private IEnumerable<AnotherThing> GetOtherThings(int arg2)
    {
        //...
        return new List<AnotherThing>();
    }
}

public class ThingHolder
{
    public IIndexedReadonlyCollection<OneThing> OneThings
    {
        get;
        private set;
    }

    public IIndexedReadonlyCollection<AnotherThing> OtherThings
    {
        get;
        private set;
    }

    public ThingHolder(IEnumerable<OneThing> oneThings,
                       IEnumerable<AnotherThing> otherThings)
    {
        OneThings = oneThings.ToIndexedReadOnlyCollection();
        OtherThings = otherThings.ToIndexedReadOnlyCollection();
    }
}

#region These classes can be written once, and used everywhere
public class IndexedCollection<T> 
    : List<T>, IIndexedReadonlyCollection<T>
{
    public IndexedCollection(IEnumerable<T> items)
        : base(items)
    {
    }
}

public static class EnumerableExtensions
{
    public static IIndexedReadonlyCollection<T> ToIndexedReadOnlyCollection<T>(
        this IEnumerable<T> items)
    {
        return new IndexedCollection<T>(items);
    }
}

public interface IIndexedReadonlyCollection<out T> : IEnumerable<T>
{
    T this[int i] { get; }
}
#endregion

使用上述代码可能看起来像这样:

var things = _thingHolderDataAccess.GetThingHolderForSomeArgs(a, b);
foreach (var oneThing in things.OneThings)
{
    // do something
}
foreach (var anotherThing in things.OtherThings)
{
    // do something else
}

var specialThing = things.OneThings[c];
// do something to special thing

感谢您提供详细的回复。由于我的代码仅涉及同类集合(完全在我的控制范围内,并且只会被我控制的代码所使用),因此这对于我相当简单的需求来说有些过于繁琐了。 - cori
@cori:我完全理解。当你的代码只在本地使用时,增加额外的工作量是不值得的。 - StriplingWarrior
在这种情况下,我建议像Warren所说的那样公开IList。我认为没有必要有一个GetThing方法。 - StriplingWarrior

0

就像其他答案所指出的那样,这是限制访问数组(或列表)本身的问题。

一般来说,您不希望类的客户端能够直接修改数组本身。隐藏底层列表的实现还允许您在将来更改实现而不影响使用该类的任何客户端代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接