3

I am converting a Word VBA macro to a plugin in C#.

I have so far successfully refactored all statements, methods and properties in C#, but this one gives me a hard time:

For Each l In Application.Languages
    If InStr(LCase(l.NameLocal), LCase(Language)) > 0 Then
        Selection.LanguageID = l.ID
        Exit For
    End If
Next l

I have converted the above in C# as follows:

using Microsoft.Office;
using Microsoft.Office.Interop;
using Word = Microsoft.Office.Interop.Word;

Word.Application oWord = new Word.Application();
Word.Document oWordDoc = new Word.Document();
var Selection = oWordDoc.ActiveWindow.Selection;
string strTgtLanguage = "Hungarian";

foreach (var item in oWord.Application.Languages)
{
          if (item.NameLocal.IndexOf(strTgtLanguage)>-1) 
//The error is  ---^  here on 'NameLocal'.
          {
              Selection.LanguageID = item.ID
//And here on 'ID' -----------------------^
              break;
          }
}

The compiler error for both instances is:

'object' does not contain a definition for 'NameLocal' and no extension method 'NameLocal' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)

What is that I am doing wrong here? I thought the foreach statement properly declares the object from the collection.

Thanks in advance.

ib11
  • 2,530
  • 3
  • 22
  • 55
  • Speaking without any knowledge of the Office Interop libraries, I don't believe I see anything wrong here. Are you sure the `Application.Languages` in your C# version is the same as the one in your VB version? I see they're the same name, but are they the same type? That sounds like what's wrong. I see `oWord.Application.Languages` in C#, should that just be `Application.Languages`, for example? – Matthew Haugen May 02 '16 at 07:28
  • 1
    one error I have found `Selection.LanguageID = l.ID` should be `Selection.LanguageID = item.ID` – Mostafiz May 02 '16 at 07:30
  • That is, I don't think this is strictly an issue with the languages. You've got the correct syntax, and it's behaving as believe you expect it to. This would be a problem in VB as well, so I'd troubleshoot it the same way as you would have there. But yes, Mostafizur's comment is also correct--just a typo on the second one. – Matthew Haugen May 02 '16 at 07:31
  • Also speaking without knowledge of Office Interop, but it's likely that `Application.Languages` returns an `IEnumerable`, not an `IEnumerable`. If it's supposed to return a collection of a particular type, you can `Cast` it, i.e. `foreach (var item in oWord.Application.Languages.Cast()) { ... }`. – yaakov May 02 '16 at 07:31
  • dee might be right. According to the documentation, `Languages` implements the non-generic `IEnumerable`, so an iterator would return `Object` instances. And, if I remember correctly, VB's Object is late-bound. Changing `var` to `Language` should do the trick. – Dennis_E May 02 '16 at 07:52

3 Answers3

2

For your error code, first error seems to me that your oWord.Application.Languages does not actually return a collection field named NameLocal.

and the second one Selection.LanguageID = l.ID should be Selection.LanguageID = item.ID

Mostafiz
  • 7,243
  • 3
  • 28
  • 42
2

Replace var item with type name.

foreach (var item in oWord.Application.Languages) replace with foreach (Word.Language item in oWord.Languages)

In the first case with var the variable item is of type System.Object.

Complete for-each using Linq could look like this:

foreach (Word.Language item in oWord.Languages.Cast<Word.Language>()
.Where(item => item.NameLocal.IndexOf(strTgtLanguage, StringComparison.Ordinal) > -1))
{
    selection.LanguageID = item.ID;
    break;
}

Or the for-each can be replace with e.g. FirstOrDefault:

Word.Language hungarian =
oWord.Languages.Cast<Word.Language>()
.FirstOrDefault(item => item.NameLocal.IndexOf(strTgtLanguage, StringComparison.Ordinal) > -1);

if (hungarian != null)
    selection.LanguageID = hungarian.ID;
Daniel Dušek
  • 13,683
  • 5
  • 36
  • 51
  • isn't `var item` a local variable type inference so that the compiler would figure out the type based on the statement right side? In other words: isn't it still a strongly typed code? – user3598756 May 02 '16 at 09:07
  • @user3598756 Yes, but in the case of `foreach (var item in oWord.Application.Languages)`, the type on the right side is `object`, so `var` will be replaced by `object`. The statement `foreach (Word.Language item in oWord.Application.Languages)` does a cast. You can use `var` in `foreach (var item in oWord.Languages.Cast())` because the cast has already been done on the right side. – Dennis_E May 02 '16 at 09:35
  • thanks for the explanation. I guess I could have verified that in VS but I wasn't (and still am not!) by it, and this `var` thing always blurs me... – user3598756 May 02 '16 at 09:59
  • @dee In fact without the cast of the IEnumerable on the right side, if I change the var to the strongly typed `Word.Language' it builds. But if I try to do the `Cast` it results in the compiler error "Foreach cannot operate on a 'method group'." So what works is `foreach (Word.Language item in oWord.Application.Languages)`. – ib11 May 02 '16 at 17:01
  • Another question is which is considered better practice: the `foreach` or the `FirstOrDefault` with LINQ? – ib11 May 02 '16 at 17:07
  • @ib11 IMO becasuse the `for-each` is enumerating only until first match is found and then it is breaked i would prefere to use `FirstOrDefault`. It is more readable. Inside of `FirstOrDefault` would probably be `for-each` as well so the performace should not be different. – Daniel Dušek May 02 '16 at 19:48
1

Languages is an IEnumerable so you'll need to declare the iteration variable explicitly.

If you use the .Cast<>() method of your IEnumerable, you could access the .Any() function to avoid writing the iteration loop yourself.

Both versions given below:

using wd = Microsoft.Office.Interop.Word;

wd.Application oWord = new wd.Application();
wd.Document oWordDoc = new wd.Document();
wd.Selection oWordSelection = oWordDoc.ActiveWindow.Selection;

// foreach loop
foreach (wd.Language item in oWord.Languages)
{
    Console.WriteLine(item.ID);
    if (item.NameLocal.IndexOf("Hungarian") > -1)
    {
        oWordSelection.LanguageID = item.ID;
        break;
    }
}

// linq
if (oWord.Languages.Cast<wd.Language>().Any(lang => lang.NameLocal.IndexOf("Hungarian") > -1))
    oWordSelection.LanguageID = wd.WdLanguageID.wdHungarian;
Ambie
  • 4,872
  • 2
  • 12
  • 26
  • This gives a compiler error on the `foreach` line that `wd` is not a type or namespace. – ib11 May 02 '16 at 16:45
  • I used `using wd = Microsoft.Office.Interop.Word`. Looking at your code I think you used `word`, so just replace `wd` with `word`. – Ambie May 02 '16 at 21:43
  • Can you please just add this `using wd = Microsoft.Office.Interop.Word` to the beginning of your code, so it is clear? – ib11 May 03 '16 at 02:40
  • I accepted yours as you were the first one, even though @dee wrote the same thing. – ib11 May 03 '16 at 06:09
  • Appreciate the thought, but I think @dee posted first. Whichever you accept, I hope it solved your question. – Ambie May 03 '16 at 07:27
  • Wow, that is called fair, thanks for the heads up! I did vote you up though... :-) – ib11 May 03 '16 at 07:40