6

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

代码最初公开了各种 ActiveRecord 风格的业务对象的集合(数组)——本质上是包装数据库字段的对象。我将数组更改为通用列表,但我很好奇的代码方面是以前的开发人员对他包装的每种类型的对象都有 Get 方法,因此:

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

这些方法有好几种,我一辈子都想不出使用这种机制比直接引用事物[i] 有什么可能的优势。为了论证的缘故,让我们假设事物是公共属性,而不是公共字段(在这种情况下,它实际上是一个自动实现的属性,因此该假设实际上是正确的)。

我错过了一些明显的东西吗?甚至是深奥的东西?

更新 我可能应该澄清一下,这些集合当前是从 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()。

4

5 回答 5

6

我不知道这是否很明显,但我确实认为你错过了一些东西。

假设things是一个IList<Thing>. 然后直接暴露它(as Things)将允许调用代码调用Add, Insert,RemoveAt等。也许以前的开发人员不想允许这样做(我相信有很多充分的理由)。

即使假设它是Thing[](soAdd等不可用),将其公开仍然允许调用代码执行类似obj.Things[0] = new Thing();的操作,这可能是根据类的实现不应该允许的操作。

可以将其暴露ThingsReadOnlyCollection<Thing>可以解决大多数这些问题的方法。但归根结底是这样的:如果开发人员只想允许调用代码通过索引访问项目——仅此而已——那么提供一个GetThing方法作为这样做的手段,老实说,到目前为止是最有意义的。

现在,当然,还有这个选项:实现一个this[int]只有get访问器的属性。但这只有在所讨论的类本质上是一个专门的对象集合时才有意义Thing(即,没有您希望在类中提供访问的其他类型对象集合) 。

总而言之,我认为这种GetThing方法非常合理。

也就是说,从您提出问题的方式来看,听起来之前的开发人员确实做出了其他一些非常糟糕的决定:

  1. 如果他/她直接将things收藏品作为公共财产公开,那么……这违背了该GetThing方法的全部目的,不是吗?结果只是一个臃肿的界面(我通常认为,当您有多种方法来完成完全相同的事情时,这不是一个好兆头,除非出于某种正当理由将它们明确记录为别名)。[更新:似乎以前的开发人员没有这样做。好的。]
  2. 看起来以前的开发人员也在内部things使用该方法访问项目GetThing,这很愚蠢(在我看来)。为什么从类本身内部使用类的公共接口引入额外方法调用的无意义开销?如果您在课堂上,那么您已经在实现中,并且可以随心所欲地访问私有/受保护的数据——无需假装。
于 2010-07-01T15:15:54.927 回答
2

这里有两点需要注意。首先是您希望将对象变量保持私有并使用 getter 和 setter 来访问它们。这可以防止用户意外更改或修改对象变量。

其次,它被认为是一个很好的命名约定,其中必须在直接访问属性的情况下使用术语 get/set。这有助于提高可读性。

尽管在这种情况下它是一个公共属性,但 getter 和 setter 的使用提高了可读性并有助于防止意外行为。如果您在循环中访问对象变量并不重要,您应该继续使用GetThing约定。

最后,如果您从对象内部访问变量,则不需要使用 getter 或 setter。

注意:它通常被认为是保持对象变量私有并为所有语言使用 getter/setter 的好风格

C++ 风格指南

C# 风格指南

您可能还对“ c# 类中的类的 getter 和 setter ”这个问题感兴趣

于 2010-07-01T15:09:54.620 回答
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");
于 2010-07-01T15:15:26.953 回答
1

如果我不得不猜测,我会说这个开发人员已经习惯了 Java 等其他语言,并且不完全了解 C# 中的标准实践。“get[Property]”命名法在 Java、javascript 等中被大量使用。C# 用属性和索引器代替了它。属性与 getter 和 setter 一样强大,但更易于编写和使用。您通常在 C# 中看到“Get[something]”的唯一情况是:

  • 该操作可能非常昂贵,以至于您真的想开车回家,因为这不是简单的成员访问(例如GetPrimeNumbers()),或者
  • 您的收藏实际上包括多个索引收藏。(例如GetRow(int i)GetColumn(int i))。即使在这种情况下,更常见的做法是将这些索引集合中的每一个简单地公开为自身的属性,它是索引类型(“ table.Rows[2]”)。

如果您在循环中访问这些值for,则该集合应实现IEnumerable<Thing>,这将使您能够访问 LINQ 方法和foreach构造。如果您仍然需要基于索引的 getter,您应该考虑使用您自己的接口 extends IEnumerable<T>,但另外提供:

T this[int i] { get; }

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

更新

我知道这主要是风格问题,这是有争议的,但我真的认为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
于 2010-07-01T15:56:35.920 回答
0

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

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

于 2010-07-01T17:18:28.193 回答