Home > database >  Why two reference of the same list behave like if they are two deep copy even if not?
Why two reference of the same list behave like if they are two deep copy even if not?

Time:10-17

In my architecture, i have business object (BO from now on), that return a standard result of type OperationResult<some_generic_type> so that each BO result is provided with instrumented information (operation stauts success/fail, exception , operation alias, error code .. and so on). To make this possible each BO calls is mediated by an object called 'manager' that will wrap the BO result into a OperationResult.

Even if encapsulated by the manager, the return type is always ready when returned, in my project i will not use lazy loading or deferred execution.

That said as premise, there is the strange bheaviour i do not understand,in which two different list should be pointing to same elements, but they do not (in the comment more details) :

    var opResult = manager.Execute(userBo.FindUser, token, query);
    //userBo.FindUser will return data inside a custom type that is "paged" list
    //each page is not of type list but IEnumerable instead

    if (opResult.Success && opResult.ReturnData != null && opResult.ReturnData.PageContent != null)
    {
        request.ItemCountAfterProcessing = opResult.ReturnData.ItemsCount;
        request.ItemCountInPage = opResult.ReturnData.ActualItemsPerPage;

        var users = opResult.ReturnData.PageContent.ToList();
        //here i get the page as List, keep in mind that datasource was already a list but my custom
        //'BasePageResults' type represent the page content as IEnumerable<T> for conveninece
        //In the following instruction i decorate 'users' list with contact information about such users
        //Everything work correctly and after decoration each user has its own contact information attached

        var usersIds = users.Select(usr => usr.Id).ToList();

        var contactQuery = new PagedQueryDto<tbl_usr_Contact> ( addr => usersIds.Contains(addr.USER_ID) );

        var opContactFetchResult = manager.Execute(userBo.FindAddressBook, token, contactQuery);
        if (opContactFetchResult.Success && opContactFetchResult.ReturnData != null && opContactFetchResult.ReturnData.PageContent != null)
        { 
            Dictionary<int, ContactDto> indexedContacts = opContactFetchResult.ReturnData.GroupBy ( addr => addr.UserId )
                                                                                         .ToDictionary ( group => group.Key , group => group.FirstOrDefault() );

            foreach (var user in users)
                if (indexedContacts.ContainsKey(user.Id))
                    user.Contact = indexedContacts[user.Id];
        }

        var newListWithSameReference = opResult.ReturnData.PageContent.ToList();
        //if now i inspect 'users' list i can find that each user has its contacts attached
        //if now i inspect 'newListWithSameReference' the users appear to be in initial state (no contact information)
        //What is not cler to me is that both variable point to the same list reference 'opResult.ReturnData.PageContent'
        //In fact 'userBo.FindUser' return a paged list, where each page is a List<T> but is seen as IEnumerable<T>  
        //only due to the fact that i use the type BasePageResults in the signature (as return type)         

        result = opResult.ReturnData.PageContent.ToList().Select ( usr => new DestinationUserDto ( usr) ).ToList();
    }

    return result;

I know i may be a bit unclear about the type involved, just for clarity i add here the custom paged list type definition and FindUser method

Here the paged list definition :

public class BasePageResults<TEntity> : IEnumerable<TEntity> where TEntity : new()
{
    public TEntity this[int index] 
    { 
        get
        {
            if (index >= 0 && index < (this.PageContent?.Count() ?? 0))
                this.PageContent.ElementAt(index);

            throw new IndexOutOfRangeException();
        }

        set
        {
            if (index >= 0 && index < (this.PageContent?.Count() ?? 0))
            {
                var temporaryList = new List<TEntity>(this.PageContent);
                temporaryList[index] = value;

                this.PageContent = temporaryList;
            }

            throw new IndexOutOfRangeException();
        }
    }

    /// <summary>
    /// Content of the current query page
    /// </summary>
    public IEnumerable<TEntity> PageContent { get; set; }

    /// <summary>
    /// The current page number
    /// </summary>
    public int PageNumber { get; set; }

    /// <summary>
    /// Indicate how many items should be in the page
    /// </summary>
    public int ItemsPerPage { get; set; }

    /// <summary>
    /// Indicate how many items there are (actually) in the page
    /// </summary>
    public int ActualItemsPerPage { get { return this.PageContent?.Count() ?? 0; } }

    /// <summary>
    /// Define how many items match the query regardlss of how many items are currently placed in the current page
    /// </summary>
    public long ItemsCount { get; set; }

    /// <summary>
    /// Define how many page there are in total
    /// </summary>
    public int PagesCount { get { return this.ItemsPerPage <= 0 ? 0 : (int)Math.Ceiling((double)this.ItemsCount / (double)this.ItemsPerPage ); } }

    public IEnumerator<TEntity> GetEnumerator()
    {
        return this.PageContent?.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.PageContent?.GetEnumerator();
    } 
}

Here the FindUser structure :

    /// <summary>
    /// Apply a query on user repository to find corresponding UserDto.
    /// Result are presented in pages
    /// </summary>
    /// <param name="query">The query to apply to datasource</param>
    /// <returns>The page searched  of Users</returns>
    [PermissionRequired(PermissionAttribute.Login | PermissionAttribute.Read)]
    [Intent(IntentDescription.Read)]
    public BasePageResults<UserDto> FindUser(PagedQueryDto<tbl_usr_User> query)
    {
        if (query == null)
            throw new ExtendedArgumentException("query");

        using (var context = ServiceLocator.ConnectionProvider.Instace<UserRoleDataContext>())
        {
            var repository = new UserRepository(context);
            var dbQuery = repository.Read(query.Query);

            var page = base.GenericPagedRead(dbQuery, query);

            return new BasePageResults<UserDto> ()
            {
                ItemsCount   = page?.ItemsCount    ?? 0,
                ItemsPerPage = page?.ItemsPerPage  ?? 0,
                PageNumber   = page?.PageNumber    ?? 0,
                PageContent  = page?.PageContent?.Select ( usr => (new UserDto()).Feed(usr) ) ?? new List<UserDto> () 
               //page?.PageContent is already a list provided by ORM that will then mapped in DTO, so the return type is definitely a List and is not deferred or lazy loading here. ORM has already done his work when i get there
            };
        }
    }

It's really beyond my comprehension why 'users' and 'newListWithSameReference' variables act like they are two deep copied variable (they should be shallow copy of the same values inside two different list, if i change a property in the first element of 'user' list the corresponding elment of 'newListWithSameReference' should change)

CodePudding user response:

There is a lot of code that makes it hard for a reader to find relevant bits. To demonstrate your issue you could just call opResult.ReturnData.PageContent.ToList(); twice then modify the first element of the first list and show it stays untouched in the second list.

Now for the problem. Look at this line in FindUsermethod.

return new BasePageResults<UserDto> ()
{
   PageContent  = page?.PageContent?.Select ( usr => (new UserDto()).Feed(usr) ) ?? new List<UserDto> ()
};

Even though page?.PageContent is a list, the BasePageResults<UserDto>.PageContent is IEnumerable<UserDto> and will create new UserDto each time you access PageContent property.

To get desired behavior add ToList after Select

return new BasePageResults<UserDto> ()
{
   PageContent  = page?.PageContent?.Select ( usr => (new UserDto()).Feed(usr) ).ToList() ?? new List<UserDto> ()
};
  • Related