编程就是不断写BUG的过程,许多东西并不是不懂,但是有时候就是想不起来,看不出来,改不过来。
今天在使用foreach的时候,程序报了一段错误,大概意思就是遍历器出了毛病,到网上查了查,原来是因为Foreach的只读性,在Foreach中是不能对迭代变量item就行修改的,只需要把foreach改用for循环就OK。
第二个问题就麻烦了直接上代码
public List<RoomMemberInfo> rmiList = new List<RoomMemberInfo>();
public Action<List<RoomMemberInfo>> ReceiveAction;
public void BroadRoomInfo()
{
foreach (var item in ClientList)
{
RoomMemberInfo info = new RoomMemberInfo()
{
id = item.Key,
name = item.Value.username,
otherInfo = ""
};
rmiList.Add(info);
}
ReceiveAction(rmiList);
string json = JsonMapper.ToJson(rmiList);
rmiList.Clear();
BroadCast(json);
}
这时最近自己在写一个类似CS的局域网联机大厅的一段用于广播的代码,其中的rmiList是服务端上存储的房间信息,由于广播方法实在每次收到新的客户端连接时调用的,所以我在每次广播后对rmiList进行了Clear()但是当我在服务端去用信息更新面板的时候问题发生了。
List<RoomMemberInfo> list = new List<RoomMemberInfo>();
void Start()
{
EthernetManager.instance.ReceiveAction += UpdateRoomPanelInfo;
}
public void UpdateRoomPanelInfo(List<RoomMemberInfo> list)
{
this.list = list;
}
void Update()
{
if (list.Count == 0)
return;
roomNumber.text = list.Count.ToString() + "/" + "8";
for (int i = 0; i < list.Count; i++)
{
for (int j = 0; j < content.childCount; j++)
{
Destroy(content.GetChild(i).gameObject);
}
PlayerUI playerUI = Instantiate(Resources.Load<PlayerUI>("Prefabs/playerUI"));
playerUI.transform.SetParent(content, false);
playerUI.Init(list[i]);
}
list.Clear();
}
由于调用广播是在一个用于监听的线程中调用,Unity不允许在线程中操作组件,所以我在事件中注册了UpdateRoomPanelnfo方法,把list值赋给了类的一个成员变量,然后在Update中进行了检测,如果有值则执行后面的方法,这时候问题来了,在我进行测试的时候有时候会执行成功实例化出PlayerUI,有时候就会各种报错,有的时候既不报错也不执行,在打断点调试的时候又基本都能成功。
每次编程的时候都会感觉直接报错或者出现明显的运行错误都好处理,只有这种有时候没问题有时候又出问题的是最难处理。
经过几个小时的反复调试,最后突然灵光一闪,发现自己烦了一个基础的引用传递问题,如果List中存储的是对象,List在传递的时候就是一个引用的地址传递,所以在上面代码中UpdateRoomPanelInfo()方法执行后,list就是rmiList,所以如果Update执行一次的时间长于BroadCast方法中执行到Clear()的时间,list就会从有变无,导致update判断失败,这也是为什么在打断点测试的时候成功率很高因为打断点可以让后者执行完再执行前者。
找到了问题的关键在于List传递了引用而不是值,一种解决方案是采用深度复制,而不是直接传递,方法这位博主讲的很清楚了。
http://blog.csdn.net/xjavasunjava/article/details/7648242
我则是采取了第二种取巧的方案,直接
ReceiveAction(rmiList);
string json = JsonMapper.ToJson(rmiList);
Thread.Sleep(2000);
rmiList.Clear();
等待2s,在执行Clear这时候该实例化的对象都搞定了,在执行清空操作就没问题了,当然要记得去掉后面的list.Clear()
刚发现只需要把rmiList.Clear()这句直接删掉,前面的方法都走了弯路,写代码写久了是真的犯蠢