for vs. foreach vs. LINQ

When I write code in Visual Studio, ReSharper (God bless it!) often suggests me to change my old-school for loop in the more compact foreach form.

And often, when I accept this change, ReSharper goes a step forward, and suggests me to change it again, in a shiny LINQ form.

So, I wonder: are there some real advantages, in these improvements? In pretty simple code execution, I cannot see any speed boost (obviously), but I can see the code becoming less and less readable… So I wonder: is it worth it?

4

for vs. foreach

There is a common confusion that those two constructs are very similar and that both are interchangeable like this:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>foreach (var c in collection)
{
DoSomething(c);
}
</code>
<code>foreach (var c in collection) { DoSomething(c); } </code>
foreach (var c in collection)
{
    DoSomething(c);
}

and:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>for (var i = 0; i < collection.Count; i++)
{
DoSomething(collection[i]);
}
</code>
<code>for (var i = 0; i < collection.Count; i++) { DoSomething(collection[i]); } </code>
for (var i = 0; i < collection.Count; i++)
{
    DoSomething(collection[i]);
}

The fact that both keywords start by the same three letters doesn’t mean that semantically, they are similar. This confusion is extremely error-prone, especially for beginners. Iterating through a collection and doing something with the elements is done with foreach; for doesn’t have to and shouldn’t be used for this purpose, unless you really know what you’re doing.

Let’s see what’s wrong with it with an example. At the end, you’ll find the full code of a demo application used to gather the results.

In the example, we are loading some data from the database, more precisely the cities from Adventure Works, ordered by name, before encountering “Boston”. The following SQL query is used:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>select distinct [City] from [Person].[Address] order by [City]
</code>
<code>select distinct [City] from [Person].[Address] order by [City] </code>
select distinct [City] from [Person].[Address] order by [City]

The data is loaded by ListCities() method which returns an IEnumerable<string>. Here is what foreach looks like:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>foreach (var city in Program.ListCities())
{
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
</code>
<code>foreach (var city in Program.ListCities()) { Console.Write(city + " "); if (city == "Boston") { break; } } </code>
foreach (var city in Program.ListCities())
{
    Console.Write(city + " ");

    if (city == "Boston")
    {
        break;
    }
}

Let’s rewrite it with a for, assuming that both are interchangeable:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>var cities = Program.ListCities();
for (var i = 0; i < cities.Count(); i++)
{
var city = cities.ElementAt(i);
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
</code>
<code>var cities = Program.ListCities(); for (var i = 0; i < cities.Count(); i++) { var city = cities.ElementAt(i); Console.Write(city + " "); if (city == "Boston") { break; } } </code>
var cities = Program.ListCities();
for (var i = 0; i < cities.Count(); i++)
{
    var city = cities.ElementAt(i);

    Console.Write(city + " ");

    if (city == "Boston")
    {
        break;
    }
}

Both return the same cities, but there is a huge difference.

  • When using foreach, ListCities() is called one time and yields 47 items.
  • When using for, ListCities() is called 94 times and yields 28153 items overall.

What happened?

IEnumerable is lazy. It means that it will do the work only at the moment when the result is needed. Lazy evaluation is a very useful concept, but has some caveats, including the fact that it’s easy to miss the moment(s) where the result will be needed, especially in the cases where the result is used multiple times.

In a case of a foreach, the result is requested only once. In a case of a for as implemented in the incorrectly written code above, the result is requested 94 times, i.e. 47 × 2:

  • Every time cities.Count() is called (47 times),

  • Every time cities.ElementAt(i) is called (47 times).

Querying a database 94 times instead of one is terrible, but not the worse thing which may happen. Imagine, for example, what would happen if the select query would be preceded by a query which also inserts a row in the table. Right, we would have for which will call the database 2,147,483,647 times, unless it hopefully crashes before.

Of course, my code is biased. I deliberately used the laziness of IEnumerable and wrote it in a way to repeatedly call ListCities(). One can note that a beginner will never do that, because:

  • The IEnumerable<T> doesn’t have the property Count, but only the method Count(). Calling a method is scary, and one can expect its result to not be cached, and not suitable in a for (; ...; ) block.

  • The indexing is unavailable for IEnumerable<T> and it’s not obvious to find the ElementAt LINQ extension method.

Probably most beginners would just convert the result of ListCities() to something they are familiar with, like a List<T>.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>var cities = Program.ListCities();
var flushedCities = cities.ToList();
for (var i = 0; i < flushedCities.Count; i++)
{
var city = flushedCities[i];
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
</code>
<code>var cities = Program.ListCities(); var flushedCities = cities.ToList(); for (var i = 0; i < flushedCities.Count; i++) { var city = flushedCities[i]; Console.Write(city + " "); if (city == "Boston") { break; } } </code>
var cities = Program.ListCities();
var flushedCities = cities.ToList();
for (var i = 0; i < flushedCities.Count; i++)
{
    var city = flushedCities[i];

    Console.Write(city + " ");

    if (city == "Boston")
    {
        break;
    }
}

Still, this code is very different from the foreach alternative. Again, it gives the same results, and this time the ListCities() method is called only once, but yields 575 items, while with foreach, it yielded only 47 items.

The difference comes from the fact that ToList() causes all data to be loaded from the database. While foreach requested only the cities before “Boston”, the new for requires all cities to be retrieved and stored in memory. With 575 short strings, it probably doesn’t make much difference, but what if we were retrieving only few rows from a table containing billions of records?

So what is foreach, really?

foreach is closer to a while loop. The code I previously used:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>foreach (var city in Program.ListCities())
{
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
</code>
<code>foreach (var city in Program.ListCities()) { Console.Write(city + " "); if (city == "Boston") { break; } } </code>
foreach (var city in Program.ListCities())
{
    Console.Write(city + " ");

    if (city == "Boston")
    {
        break;
    }
}

can be simply replaced by:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>using (var enumerator = Program.ListCities().GetEnumerator())
{
while (enumerator.MoveNext())
{
var city = enumerator.Current;
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
}
</code>
<code>using (var enumerator = Program.ListCities().GetEnumerator()) { while (enumerator.MoveNext()) { var city = enumerator.Current; Console.Write(city + " "); if (city == "Boston") { break; } } } </code>
using (var enumerator = Program.ListCities().GetEnumerator())
{
    while (enumerator.MoveNext())
    {
        var city = enumerator.Current;
        Console.Write(city + " ");

        if (city == "Boston")
        {
            break;
        }
    }
}

Both produce the same IL. Both have the same result. Both have the same side effects. Of course, this while can be rewritten in a similar infinite for, but it would be even longer and error-prone. You’re free to choose the one you find more readable.

Want to test it yourself? Here’s the full code:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
public class Program
{
private static int countCalls;
private static int countYieldReturns;
public static void Main()
{
Program.DisplayStatistics("for", Program.UseFor);
Program.DisplayStatistics("for with list", Program.UseForWithList);
Program.DisplayStatistics("while", Program.UseWhile);
Program.DisplayStatistics("foreach", Program.UseForEach);
Console.WriteLine("Press any key to continue...");
Console.ReadKey(true);
}
private static void DisplayStatistics(string name, Action action)
{
Console.WriteLine("--- " + name + " ---");
Program.countCalls = 0;
Program.countYieldReturns = 0;
var measureTime = Stopwatch.StartNew();
action();
measureTime.Stop();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("The data was called {0} time(s) and yielded {1} item(s) in {2} ms.", Program.countCalls, Program.countYieldReturns, measureTime.ElapsedMilliseconds);
Console.WriteLine();
}
private static void UseFor()
{
var cities = Program.ListCities();
for (var i = 0; i < cities.Count(); i++)
{
var city = cities.ElementAt(i);
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
}
private static void UseForWithList()
{
var cities = Program.ListCities();
var flushedCities = cities.ToList();
for (var i = 0; i < flushedCities.Count; i++)
{
var city = flushedCities[i];
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
}
private static void UseForEach()
{
foreach (var city in Program.ListCities())
{
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
}
private static void UseWhile()
{
using (var enumerator = Program.ListCities().GetEnumerator())
{
while (enumerator.MoveNext())
{
var city = enumerator.Current;
Console.Write(city + " ");
if (city == "Boston")
{
break;
}
}
}
}
private static IEnumerable<string> ListCities()
{
Program.countCalls++;
using (var connection = new SqlConnection("Data Source=mframe;Initial Catalog=AdventureWorks;Integrated Security=True"))
{
connection.Open();
using (var command = new SqlCommand("select distinct [City] from [Person].[Address] order by [City]", connection))
{
using (var reader = command.ExecuteReader(CommandBehavior.SingleResult))
{
while (reader.Read())
{
Program.countYieldReturns++;
yield return reader["City"].ToString();
}
}
}
}
}
}
</code>
<code>using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Diagnostics; using System.Linq; public class Program { private static int countCalls; private static int countYieldReturns; public static void Main() { Program.DisplayStatistics("for", Program.UseFor); Program.DisplayStatistics("for with list", Program.UseForWithList); Program.DisplayStatistics("while", Program.UseWhile); Program.DisplayStatistics("foreach", Program.UseForEach); Console.WriteLine("Press any key to continue..."); Console.ReadKey(true); } private static void DisplayStatistics(string name, Action action) { Console.WriteLine("--- " + name + " ---"); Program.countCalls = 0; Program.countYieldReturns = 0; var measureTime = Stopwatch.StartNew(); action(); measureTime.Stop(); Console.WriteLine(); Console.WriteLine(); Console.WriteLine("The data was called {0} time(s) and yielded {1} item(s) in {2} ms.", Program.countCalls, Program.countYieldReturns, measureTime.ElapsedMilliseconds); Console.WriteLine(); } private static void UseFor() { var cities = Program.ListCities(); for (var i = 0; i < cities.Count(); i++) { var city = cities.ElementAt(i); Console.Write(city + " "); if (city == "Boston") { break; } } } private static void UseForWithList() { var cities = Program.ListCities(); var flushedCities = cities.ToList(); for (var i = 0; i < flushedCities.Count; i++) { var city = flushedCities[i]; Console.Write(city + " "); if (city == "Boston") { break; } } } private static void UseForEach() { foreach (var city in Program.ListCities()) { Console.Write(city + " "); if (city == "Boston") { break; } } } private static void UseWhile() { using (var enumerator = Program.ListCities().GetEnumerator()) { while (enumerator.MoveNext()) { var city = enumerator.Current; Console.Write(city + " "); if (city == "Boston") { break; } } } } private static IEnumerable<string> ListCities() { Program.countCalls++; using (var connection = new SqlConnection("Data Source=mframe;Initial Catalog=AdventureWorks;Integrated Security=True")) { connection.Open(); using (var command = new SqlCommand("select distinct [City] from [Person].[Address] order by [City]", connection)) { using (var reader = command.ExecuteReader(CommandBehavior.SingleResult)) { while (reader.Read()) { Program.countYieldReturns++; yield return reader["City"].ToString(); } } } } } } </code>
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;

public class Program
{
    private static int countCalls;

    private static int countYieldReturns;

    public static void Main()
    {
        Program.DisplayStatistics("for", Program.UseFor);
        Program.DisplayStatistics("for with list", Program.UseForWithList);
        Program.DisplayStatistics("while", Program.UseWhile);
        Program.DisplayStatistics("foreach", Program.UseForEach);

        Console.WriteLine("Press any key to continue...");
        Console.ReadKey(true);
    }

    private static void DisplayStatistics(string name, Action action)
    {
        Console.WriteLine("--- " + name + " ---");

        Program.countCalls = 0;
        Program.countYieldReturns = 0;

        var measureTime = Stopwatch.StartNew();
        action();
        measureTime.Stop();

        Console.WriteLine();
        Console.WriteLine();
        Console.WriteLine("The data was called {0} time(s) and yielded {1} item(s) in {2} ms.", Program.countCalls, Program.countYieldReturns, measureTime.ElapsedMilliseconds);
        Console.WriteLine();
    }

    private static void UseFor()
    {
        var cities = Program.ListCities();
        for (var i = 0; i < cities.Count(); i++)
        {
            var city = cities.ElementAt(i);

            Console.Write(city + " ");

            if (city == "Boston")
            {
                break;
            }
        }
    }

    private static void UseForWithList()
    {
        var cities = Program.ListCities();
        var flushedCities = cities.ToList();
        for (var i = 0; i < flushedCities.Count; i++)
        {
            var city = flushedCities[i];

            Console.Write(city + " ");

            if (city == "Boston")
            {
                break;
            }
        }
    }

    private static void UseForEach()
    {
        foreach (var city in Program.ListCities())
        {
            Console.Write(city + " ");

            if (city == "Boston")
            {
                break;
            }
        }
    }

    private static void UseWhile()
    {
        using (var enumerator = Program.ListCities().GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                var city = enumerator.Current;
                Console.Write(city + " ");

                if (city == "Boston")
                {
                    break;
                }
            }
        }
    }

    private static IEnumerable<string> ListCities()
    {
        Program.countCalls++;
        using (var connection = new SqlConnection("Data Source=mframe;Initial Catalog=AdventureWorks;Integrated Security=True"))
        {
            connection.Open();

            using (var command = new SqlCommand("select distinct [City] from [Person].[Address] order by [City]", connection))
            {
                using (var reader = command.ExecuteReader(CommandBehavior.SingleResult))
                {
                    while (reader.Read())
                    {
                        Program.countYieldReturns++;
                        yield return reader["City"].ToString();
                    }
                }
            }
        }
    }
}

And the results:

— for —
Abingdon Albany Alexandria Alhambra […] Bonn Bordeaux Boston

The data was called 94 time(s) and yielded 28153 item(s).

— for with list —
Abingdon Albany Alexandria Alhambra […] Bonn Bordeaux Boston

The data was called 1 time(s) and yielded 575 item(s).

— while —
Abingdon Albany Alexandria Alhambra […] Bonn Bordeaux Boston

The data was called 1 time(s) and yielded 47 item(s).

— foreach —
Abingdon Albany Alexandria Alhambra […] Bonn Bordeaux Boston

The data was called 1 time(s) and yielded 47 item(s).

LINQ vs. traditional way

As for LINQ, you may want to learn functional programming (FP) – not C# FP stuff, but real FP language like Haskell. Functional languages have a specific way to express and present the code. In some situations, it is superior to non-functional paradigms.

FP is known being much superior when it comes to manipulating lists (list as a generic term, unrelated to List<T>). Given this fact, the ability to express C# code in a more functional way when it comes to lists is rather a good thing.

If you’re not convinced, compare the readability of code written in both functional and non-functional ways in my previous answer on the subject.

11

While there are some great expositions already on the differences between for and foreach. There are some gross misrepresentations of the role of LINQ.

LINQ syntax is not just syntactic sugar giving a functional programming approximation to C#. LINQ provides Functional constructs including all the benefits thereof to C#. Combined with returning IEnumerable instead of IList, LINQ provides deferred execution of the iteration. What people typically do now is construct and return an IList from their functions like so

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public IList<Foo> GetListOfFoo()
{
var retVal=new List<Foo>();
foreach(var foo in _myPrivateFooList)
{
if(foo.DistinguishingValue == check)
{
retVal.Add(foo);
}
}
return retVal;
}
</code>
<code>public IList<Foo> GetListOfFoo() { var retVal=new List<Foo>(); foreach(var foo in _myPrivateFooList) { if(foo.DistinguishingValue == check) { retVal.Add(foo); } } return retVal; } </code>
public IList<Foo> GetListOfFoo()
{
   var retVal=new List<Foo>();
   foreach(var foo in _myPrivateFooList)
   {
      if(foo.DistinguishingValue == check)
      {
         retVal.Add(foo);
      }
   }
   return retVal;
}

Instead use the yield return syntax to create a deferred enumeration.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public IEnumerable<Foo> GetEnumerationOfFoo()
{
//no need to create an extra list
//var retVal=new List<Foo>();
foreach(var foo in _myPrivateFooList)
{
if(foo.DistinguishingValue == check)
{
//yield the match compiler handles the complexity
yield return foo;
}
}
//no need for returning a list
//return retVal;
}
</code>
<code>public IEnumerable<Foo> GetEnumerationOfFoo() { //no need to create an extra list //var retVal=new List<Foo>(); foreach(var foo in _myPrivateFooList) { if(foo.DistinguishingValue == check) { //yield the match compiler handles the complexity yield return foo; } } //no need for returning a list //return retVal; } </code>
public IEnumerable<Foo> GetEnumerationOfFoo()
{
   //no need to create an extra list
   //var retVal=new List<Foo>();
   foreach(var foo in _myPrivateFooList)
   {
      if(foo.DistinguishingValue == check)
      {
         //yield the match compiler handles the complexity
         yield return foo;
      }
   }
   //no need for returning a list
   //return retVal;
}

Now the enumeration won’t occur until you ToList or iterate over it. And it only occurs as needed (here’s an enumeration of Fibbonaci that doesn’t have a stack overflow problem)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>/**
Returns an IEnumerable of fibonacci sequence
**/
public IEnumerable<int> Fibonacci()
{
int first, second = 1;
yield return first;
yield return second;
//the 46th fibonacci number is the largest that
//can be represented in 32 bits.
for (int i = 3; i < 47; i++)
{
int retVal = first+second;
first=second;
second=retVal;
yield return retVal;
}
}
</code>
<code>/** Returns an IEnumerable of fibonacci sequence **/ public IEnumerable<int> Fibonacci() { int first, second = 1; yield return first; yield return second; //the 46th fibonacci number is the largest that //can be represented in 32 bits. for (int i = 3; i < 47; i++) { int retVal = first+second; first=second; second=retVal; yield return retVal; } } </code>
/**
Returns an IEnumerable of fibonacci sequence
**/
public IEnumerable<int> Fibonacci()
{
  int first, second = 1;
  yield return first;
  yield return second;
  //the 46th fibonacci number is the largest that
  //can be represented in 32 bits. 
  for (int i = 3; i < 47; i++)
  {
    int retVal = first+second;
    first=second;
    second=retVal;
    yield return retVal;
  }
}

Performing a foreach over the Fibonacci function will return the sequence of 46. If you want the 30th that’s all that will be calculated

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>var thirtiethFib=Fibonacci().Skip(29).Take(1);
</code>
<code>var thirtiethFib=Fibonacci().Skip(29).Take(1); </code>
var thirtiethFib=Fibonacci().Skip(29).Take(1);

Where we get to have a lot of fun is the support in the language for lambda expressions (combined with the IQueryable and IQueryProvider constructs, this allows for functional composition of queries against a variety of data sets, the IQueryProvider is responsible for interpreting the passed in expressions and creating and executing a query using the source’s native constructs). I won’t go into the nitty gritty details here, but there is a series of blog posts showing how to create a SQL Query Provider here

In summary, you should prefer returning IEnumerable over IList when the consumers of your function will perform a simple iteration. And use the capabilities of LINQ to defer execution of complex queries until they are needed.

but I can see the code becoming less and less readable

Readability is in the eye of the beholder. Some people might say

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>var common = list1.Intersect(list2);
</code>
<code>var common = list1.Intersect(list2); </code>
var common = list1.Intersect(list2);

is perfectly readable; others might say that this is opaque, and would prefer

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>List<int> common = new List<int>();
for(int i1 = 0; i1 < list1.Count; i1++)
{
for(int i2 = 0; i2 < list2.Count; i2++)
{
if (list1[i1] == list2[i2])
{
common.Add(i1);
break;
}
}
}
</code>
<code>List<int> common = new List<int>(); for(int i1 = 0; i1 < list1.Count; i1++) { for(int i2 = 0; i2 < list2.Count; i2++) { if (list1[i1] == list2[i2]) { common.Add(i1); break; } } } </code>
List<int> common = new List<int>();
for(int i1 = 0; i1 < list1.Count; i1++)
{
    for(int i2 = 0; i2 < list2.Count; i2++)
    {
        if (list1[i1] == list2[i2])
        {
            common.Add(i1);
            break;
        }
    }
}

as making it clearer what’s being done. We can’t tell you what you find more readable. But you might be able to detect some of my own bias in the example I have constructed here…

8

The difference between LINQ and foreach really boils down to two different programming styles: imperative and declarative.

  • Imperative: in this style you tell the computer “do this… now do this… now do this now do this”. You feed it a program one step at a time.

  • Declarative: in this style you tell the computer what you want the result to be and let it figure out how to get there.

A classic example of these two styles is comparing assembly code (or C) with SQL. In assembly you give instructions (literally) one at a time. In SQL you express how to join data together and what result you want from that data.

A nice side-effect of declarative programming is that it tends to be a bit higher level. This allows for the platform to evolve underneath you without you having to change your code. For instance:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>var foo = bar.Distinct();
</code>
<code>var foo = bar.Distinct(); </code>
var foo = bar.Distinct();

What is happening here? Is Distinct using one core? Two? Fifty? We don’t know and we don’t care. The .NET devs could rewrite it at any time, as long as it continues to perform the same purpose our code could just magically get faster after a code update.

This is the power of functional programming. And the reason that you’ll find that code in languages like Clojure, F# and C# (written with a functional programming mindset) is often 3x-10x smaller than it’s imperative counterparts.

Finally I like the declarative style because in C# most of the time this allows me to write code that does not mutate the data. In the above example, Distinct() does not change bar, it returns a new copy of the data. This means that whatever bar is, and where ever it came from, it not suddenly changed.

So like the other posters are saying, learn functional programming. It’ll change your life. And if you can, do it in a true functional programming language. I prefer Clojure, but F# and Haskell are also excellent choices.

1

Can other developers in the team read LINQ?

If not then don’t use it or one of two things will happen:

  1. Your code will be unmaintainable
  2. You’ll be stuck with maintaining all your code and everything which relies on it

A for each loop is perfect for iterating through a list but if that’s not what you what to do then don’t use one.

12

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật