【问题标题】:c# Method not working as expected Address book projectc#方法没有按预期工作通讯簿项目
【发布时间】:2017-05-02 00:57:28
【问题描述】:

我是 C# 新手,正在开发我的第一个项目 - 一个控制台应用程序。当地址簿中的条目已经存在时,我无法理解为什么我的代码不会返回 false。以下方法都是 AddressBook 类 CheckEntry()、AddEntry()、RemoveEntry() 的一部分。

好的,所以布尔方法 CheckEntry() 被另外两个方法使用 - AddEntry() 和 RemoveEntry() - 两者都在执行各自的职责之前验证用户条目是否存在。在 AddEntry() 中,它应该在添加另一个联系人之前查看该联系人是否已经存在,如果存在则不应创建该联系人(但它正在添加重复项)。 RemoveEntry() 应该检查它是否存在,并使用 CheckEntry() 中存储的变量的更新值来删除当前联系人(但什么也不做)。 我知道我可能要么遗漏了一些简单的东西,要么对整个过程进行了思考。我的假设是 checkEntry() 无法正常工作,因为与之相关的两个函数都出现故障。有人有想法么??如果我需要进一步解释,请告诉我。

注意:我知道使用列表会更有效/更容易使用。我的目标是使用数组进行学习。从学习 Javascript 切换到 C# 有点挑战,我想确保在学习下一件事之前我正在学习每一件事。

这是我所有的代码。每个班级用“//--------”分隔,提前感谢您的帮助。

namespace AddressBook {
    class Contact {
        public string Name;
        public string Address;

        public Contact(string name, string address) {
             Name = name;
             Address = address;
        }
    }
}

//------------------------------------------------------------------------

using System;

namespace AddressBook {
    class AddressBook {

        public readonly Contact[] contacts;

        public AddressBook() {
            contacts = new Contact[2]; ;
        }

        public void AddEntry(string name, string address) {
            Contact AddContact = new Contact(name, address);
            if (CheckEntry(name)) {
                for (int i = 0; i < contacts.Length; i++) {
                    if (contacts[i] == null) {
                        contacts[i] = AddContact;
                        Console.WriteLine("Address Book updated. {0} has been added!", name);
                        break;
                    }
                }
            }
        }

        private string existingContact = "";

        private bool CheckEntry(string name) {
            foreach(Contact contact in contacts) {
                if (contact == null) {
                    break;
                }
                else if (contact != null && contact.ToString() != name) {
                    continue;
                }
                else if (contact.ToString() == name) {
                    existingContact = contact.ToString();
                    return false;
                }

            }
             return true;
        }

        public void RemoveEntry(string name) {
            if( !(CheckEntry(name)) ) {
                existingContact = null;
                Console.WriteLine("{0} removed from contacts", name);
            }
        }

         public string View() {
            string contactList = "";
            foreach(Contact contact in contacts) {
                if(contact == null) {
                    break;
                }
                contactList += String.Format("Name: {0} -- Address: {1}" + Environment.NewLine, contact.Name, contact.Address); 
            }
            return contactList;
        }
    }
}

//------------------------------------------------------------------------

using System;

namespace AddressBook {
    class Program {
        static void Main(string[] args) {

            AddressBook addressBook = new AddressBook();

            PromptUser();

            void Menu() {
                Console.WriteLine("TYPE:");
                Console.WriteLine("'Add' to add a contact: ");
                Console.WriteLine("'Remove' to select and remove a contact: ");
                Console.WriteLine("'Quit' to exit: ");
            }

            void UpdateAddressBook(string userInput) {
                string name = "";
                string address = "";
                switch ( userInput.ToLower() ) {
                    case "add":
                        Console.Write("Enter a name: ");
                        name = Console.ReadLine();
                        Console.Write("Enter an address: ");
                        address = Console.ReadLine();
                        addressBook.AddEntry(name, address);
                        break;
                    case "remove":
                        Console.Write("Enter a name to remove: ");
                        name = Console.ReadLine();
                        addressBook.RemoveEntry(name); 
                        break;
                    case "view":
                        Console.WriteLine(addressBook.View());
                        break;
                }
            }

            void PromptUser() {
                Menu();
                string userInput = "";
                while (userInput != "quit") {
                    Console.WriteLine("What would you like to do?");
                    userInput = Console.ReadLine();
                    UpdateAddressBook(userInput);
                }
            }
        }
    }
}

这是我想出的 - 经过测试的更改

现在我不能添加重复的名称,我可以删除条目。

public void AddEntry(string name, string address) {
        Contact AddContact = new Contact(); //changed
        AddContact.Name = name;  //changed
        AddContact.Address = address; //changed
        if (CheckEntry(name)) {
            for(int i = 0; i < contacts.Length; i++) {
                if (contacts[i] == null) {
                    contacts[i] = AddContact;
                    Console.WriteLine("Address Book updated. {0} has been added!", name);
                    break;
                }
            }
        }
    }
    //changed - removed variable and all instances of...
    private bool CheckEntry(string name) {
        foreach(Contact contact in contacts) {
            if (contact == null) {
                break;
            }
            else if (contact != null && contact.Name != name) {
                continue;
            }
            else if (contact.Name == name) {
                return false;
            }
        }
        return true;
    }

    //changed - instead of passing checkentry() as a check I just took care of it here
    public void RemoveEntry(string name) {
        for(int i = 0; i < contacts.Length; i++) {
            if(contacts[i].Name == name) {
                contacts[i] = null;
                break;
            }
        }
        Console.WriteLine("{0} removed from contacts", name);
    }

【问题讨论】:

  • 您是否设置了断点来在运行时计算表达式的布尔值?
  • @ChrisCruz 不,我听说过这个过程,但我还没有开始这样做。到目前为止,我一直在 Web 环境中学习。关于该过程,我认为我唯一知道的是单击代码的左边距并设置断点,但除此之外,我对它们或它们的工作方式一无所知。

标签: c# boolean private-members helpermethods


【解决方案1】:

在 CheckEntry 中,您正在使用字符串类型的参数测试 Contact 类型的对象。这不起作用,因为您正在测试 2 种不同的类型。 如果您在 Contact 类中重写 ToString 方法(以便它提供属性contact.name),它可以按照您编写的方式工作。

您可以像这样修改您的代码(使用 System.Linq 添加):

private bool CheckEntry(string name)
{
    return contacts.Any(a => a.Name == name);            
}

【讨论】:

  • 啊,是的。我花了很长时间试图弄清楚为什么它不会转换。我一看到contact.Name就点击了。我做了这些改变。我确实尝试了 Any() 但我没有成功。我认为如果 a.Name == name 它应该返回 true,否则返回 false?
  • 我明天开始时将再次尝试 Any()。我已经将我能够提出并开始工作的内容添加到原始帖子中。我试图赞成你的回答,但我想我仍然不允许......
【解决方案2】:

首先,我假设您的策略是找到数组中的第一个空槽,并在其中插入一个新的Contact 以用于AddEntry。要删除条目,您只需将该数组位置标记为空。如您所知,这意味着数组不会随请求动态增长,即您可能需要处理 ArrayFull 情况。此外,您正在对数组进行线性搜索,也就是扫描 - 我假设您不想在此示例中关注该方面。

以下是您现有代码的我的 cmets:

  1. 即使Name 匹配,如果Address 不同,AddEntry 是否应该更新Address
  2. 还返回 bool 以指示地址是否已添加/更新 (true) 与未执行任何操作 (false)
  3. 您还应该处理“ArrayFull”条件
  4. 我不明白你为什么需要变量existingContact
  5. 如果您打算返回 bool,请不要使用名为 CheckXXX 的函数。 ContainxXXX 是更好理解的方法名称,可以与 bool 返回值一起使用。
  6. 您不应该在CheckEntry 中从foreach 循环中break。如果添加了 2 个条目,然后删除了第一个条目,然后再次请求添加第二个条目怎么办?因为第一个条目是null,所以你会爆发
  7. 您似乎有 3 个不同的 ifs 可以签入您的 foreach,只有一个就足够了。

以下是我与 cmets 的相关代码。我试图让它们与你所拥有的相似。您可以在多个地方使用 LINQ 而不是循环。

class Contact {
    public string Name { get; private set; }        // use a property with a private setter, instead of a public member
    public string Address { get; private set; }     // use a property with a private setter, instead of a public member

    public Contact(string name, string address) {
        Name = name;
        Address = address;
    }

}

//------------------------------------------------------------------------


class AddressBook {

    public readonly Contact[] contacts;

    public AddressBook() {
        contacts = new Contact[2]; // I am assuming you kept the size 2 for testing
    }

    public bool AddEntry(string name, string address) {
        if (!ContainsEntry(name)) {
            Contact AddContact = new Contact(name, address);
            for (int i = 0; i < contacts.Length; i++) {
                if (contacts[i] == null) {
                    contacts[i] = AddContact;
                    Console.WriteLine("Address Book updated. {0} has been added!", name);
                    return true;
                }
            }
            Console.WriteLine($"Cannot add name ({name}) to Address Book since it is full!");
            // TODO: Throw some exception or specific return values to indicate the same to the caller
        } else {
            Console.WriteLine($"Name ({name}) already exists in Address Book!");
            // TODO: Update the address?
        }

        return false;
    }

    private int GetEntryIndex(string name) {
        for (int i = 0; i < contacts.Length; i++) {
            if (contacts[i] != null && contacts[i].Name == name)
                return i;
        }

        return -1;
    }

    private bool ContainsEntry(string name) {
        return GetEntryIndex(name) != -1;
    }

    public void RemoveEntry(string name) {
        var index = GetEntryIndex(name);
        if (index != -1) {
            contacts[index] = null;
            Console.WriteLine("{0} removed from contacts", name);
        }
    }

    public string View() {
        string contactList = "";
        foreach (Contact contact in contacts) {
            if (contact == null) {
                continue;   // Don't break, but simply continue to look further
            }
            contactList += String.Format("Name: {0} -- Address: {1}" + Environment.NewLine, contact.Name, contact.Address);
        }
        return contactList;
    }
}

编辑:回答 OP 提出的一些问题

QAddEntry的返回值我该怎么办
A:现在,你正在编写代码练习,但是一旦你开始编写行业标准代码,您很快就会意识到您不知道谁调用了您的函数。永远不要假设(除非方法是私有的)只有你会调用这个函数。目前,您不需要对这个返回值做任何事情,但是当您想知道您的调用是否确实修改了某些值或只是返回而不做任何更改时,可能会到来。就是为了那个时候。这是一种非常标准的做法。有些人甚至返回enum {NoChange, Added, Updated, Deleted} 而不是bool

Q:如何减少CheckEntry函数。

A: 下面我用稍微不同的格式编写了你的​​函数

private bool CheckEntry(string name) {
    foreach (Contact contact in contacts) {
        if (contact == null) { // First if
            continue;
        } else {
            if (contact != null && contact.Name != name) { // Second if
                continue;
            } else {
                if (contact.Name == name) { // Third if
                    return false;
                }
            }
        }
    }
    return true;
}

对于第一个 if 语句,else 部分是多余的。由于continue 语句,第二个if 语句仅在contact is not null 时才会命中,从而取消了对else 关键字的需要。

由于contact is not null 在我们点击第二个if 语句时,检查contact != null 在第二个if 中是相当多余的。您可以减少 if 语句如下

if (contact.Name != name) { // Second if
    continue;
} else {
    if (contact.Name == name) { // Third if
        return false;
    }
}

同样,您会注意到第三个if 只有在contact.Namename 相同时才会命中(否则它会继续)。所以没有必要再次检查。这将减少我们的检查,如下所示

if (contact == null)
    continue;

if (contact.Name != name)
    continue;
else
    return false;

这可以通过组合以下两个if 语句中的条件来进一步减少

if (contact == null || contact.Name != name)
    continue;
else
    return false;

与(否定条件)相同

if (contact != null && contact.Name == name)
    return false;

所以你的函数看起来像

private bool CheckEntry(string name) {
    foreach (Contact contact in contacts)
        if (contact != null && contact.Name == name)
            return false;
    return true;
}

希望你在这里扣分

Q:我可能对课程有误解,但是当你在一个带有 getter 和 setter 的类中编写属性时,我确实在我的更新版本中进行了更改,我的印象是它是创建构造函数没有必要,甚至是多余的——类(不确定这是否是正确的术语)具有内置的默认构造函数,用于您想要添加属性的情况。
A强>:正确。只是做法不同而已。

:我喜欢您使用 GetEntryIndex() 和 ContainsEntry() 所做的事情 - 我很好奇,GetEntryIndex() 与编写自己的 Array.IndexOf() 不一样吗?有人,无论是我还是方法,都必须扫描数组。两个版本都是线性的,所以无论哪种方式,这是 O(n),对吗? (只是进入一些理论,所以如果我错了请纠正我)。所以,就我的理解而言,这与:return Array.IndexOf(contacts, name);如果它不存在或索引是什么,则返回 -1(我假设)

A:不一样,但本质上是相似的。 IndexOf 有一组规则来判断给定的两个对象是否为equal。您尚未在自定义对象上定义这些规则,因此它将始终返回 -1。有一些简单的 LINQ 语句可以做到这些,但我会让你自己去探索。

【讨论】:

  • 我发布了我的回复作为答案,这样我就可以浏览你给我的所有内容,因为你只能发表这么大的评论。让我知道你的想法。
  • 在查看您的建议时,我想到了 view() 而不是在用户请求查看空列表时不返回任何内容 --return (contactList != String.Empty) ? contactList : "你的通讯录是空的。";
  • @Nibble15 是的,您可以根据需要更改退货声明。我还用您提出的后续问题更新了我的答案
【解决方案3】:

首先,感谢您抽出宝贵时间做出如此彻底的回应。其次,我可能应该注意到,我正在自学/参加有关 Treehouse 的课程,并且我在课程的第二周/第二部分 - 只是为了让您了解我来自哪里。话虽如此,我想了解一下你给我的东西以及我在这个项目中的发展方向,以便我可以学习。

  1. 我同意用户应该能够更新,这是我考虑过但尚未完全实现的功能。
  2. 如果在添加/更新时返回布尔值,您是否会删除更新字符串并将其与调用者一起放在 main() 中?这样,如果返回 true 则仅打印已更新,否则打印联系人未更改。感觉制作一个适合仅检查值的类可能是有利的——这也会使我的代码更可重用?如果我要在 Main() 中使用它,也许这样的东西会起作用..

if(!addEntry()) {Console.WriteLine("Contact {0} 未更新", name);} //做点什么 else{Console.WriteLine("通讯录已更新。{0} 已添加!", name);}

此外,如果用户只是在更新联系人,它可以打印出它已被更新。所以控制台输出可能是三元操作 - 如果添加了新的姓名打印联系人,否则联系人更新了?

  1. 我同意并且遇到了由于阵列已满而什么都没做的情况,这是我计划要做的事情。
  2. 我完全同意现有的联系变量。当谈到我能想到的最好的事情时,我被困住了。我应该走开一会儿,多想一想。我不知道你是否看到了我更新的部分,但我能够消除它。
  3. 包含 bool 方法似乎合乎逻辑,我一定会遵守这条规则。
  4. 在你这么说之前我没有考虑过这个问题。不过,这很有意义。如果第一个条目为 null 并且第二个包含用户尝试输入的名称,那么您最终会得到一个重复项,因为我在第一个 null 实例处中断了循环,而不是确保该名称不存在在整个数组中,首先。
  5. 我不确定,如果没有您的方法,我将如何消除我的三个条件。我看到你做了什么来使一个工作,但我可能错过了什么吗?或者,这更像是一个参考说法,好吧,这里有一个更好的方法 - 使用这些方法检查然后消除 if/else 链,从而使您的代码更简洁。

为您的代码:

  1. 我可能对课程有误解,但是当您在具有 getter 和 setter 的类中编写属性时,我确实在更新版本中进行了更改,我的印象是没有必要,甚至是多余的, 创建一个构造函数 - 该类(不确定这是否是正确的术语)具有内置的默认构造函数,用于您想要添加属性的情况。

  2. 正确,我确实将大小设置为 2 以进行测试。

  3. 我喜欢你对 GetEntryIndex() 和 ContainsEntry() 所做的事情——我很好奇,GetEntryIndex() 与编写自己的 Array.IndexOf() 是不是一样的?有人,无论是我还是方法,都必须扫描数组。两个版本都是线性的,所以无论哪种方式,都是 O(n),对吗? (只是进入一些理论,所以如果我错了请纠正我)。所以,就我的理解而言,这与:

return Array.IndexOf(联系人, 姓名);

如果它不存在或者无论索引是什么,这都会返回 -1(我假设)

  1. 在 View() 中使用 continue 是个好主意。这将确保打印出索引高于空值索引的任何联系人。神奇的是,它以这种方式与 break 一起工作(即使索引 0 为空,它也会打印出索引 1),但我确实意识到为什么它不应该以及如何使用 continue 更好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-03
    • 1970-01-01
    • 1970-01-01
    • 2017-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多